剑指offer题解(Java版)

时间:2021-06-04 00:27:11

剑指offer题解(Java版)

从尾到头打印链表

题目描述

输入一个链表,按从尾到头的顺序返回一个ArrayList。

方法1:用一个栈保存从头到尾访问链表的每个结点的值,然后按出栈顺序将各个值存入ArrayList中;

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<Integer> temp = new Stack<>();
ArrayList<Integer> newList = new ArrayList<>();
ListNode t = listNode;
while( t != null ){
temp.push(t.val);
t = t.next;
}
while( !temp.empty() ){
newList.add(temp.pop());
}
return newList;
}
}

方法2:逆序打印1->2->3,可以先逆序打印2->3,最后再打印第一个结点1。

而链表2->3可以看成一个新的链表,要逆序打印该链表可以递归调用求解函数;

import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> ret = new ArrayList<>();
if(listNode!=null){
ret.addAll(printListFromTailToHead(listNode.next));
ret.add(listNode.val);
}
return ret;
}
}

方法3:使用头插法构建新的链表

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
// 头插法构建逆序链表
ListNode head = new ListNode(-1);
while (listNode != null) {
ListNode Next = listNode.next;
listNode.next = head.next;
head.next = listNode;
listNode = Next;
}
ArrayList<Integer> ret = new ArrayList<>();
head = head.next;
while (head != null) {
ret.add(head.val);
head = head.next;
}
return ret;
}

重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

解题思路:前序遍历的第一个值为根节点的值,该值将中序遍历分为两部分,左部分为左子树中序遍历的结果,右部分为右子树中序遍历的结果,然后分别递归构建左右子树;

public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
TreeNode root = new TreeNode(pre[0]);
int len = pre.length;
if(len==1){
root.left=null;
root.right=null;
return root;
}
int val = root.val;
int i;
//找到根在中序中的位置
for(i=0;i<len;i++){
if(val==in[i])
break;
}
if(i>0){
//创建左子树
int[] leftPre = new int[i];
int[] leftIn = new int[i];
for(int j=0;j<i;j++){
leftPre[j]=pre[j+1];
}
for(int j=0;j<i;j++){
leftIn[j]=in[j];
}
root.left = reConstructBinaryTree(leftPre,leftIn);
}else{
root.left=null;
}
if(len-i-1>0){
int[] rightPre = new int[len-i-1];
int[] rightIn = new int[len-i-1];
for(int j=i+1;j<len;j++){
rightPre[j-i-1] = pre[j];
rightIn[j-i-1] = in[j];
}
root.right = reConstructBinaryTree(rightPre,rightIn);
}else{
root.right=null;
}
return root;
}
}

用两个栈实现队列

题目描述

用两个栈实现一个队列,完成队列的Push和Pop操作

解题思路:一个栈负责push一个栈负责pop,当队列要pop时,检查负责pop栈是否为空,不为空执行pop,否则将负责push的栈中元素全部出栈到另一个栈中再执行pop,为的是使队列pop出最老的元素;

public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
Integer del = null;
if(!stack2.empty()){
del = stack2.pop();
}else{
while(!stack1.empty()){
del = stack1.pop();
stack2.push(del);
}
if(!stack2.empty()){
del = stack2.pop();
}
}
return del;
}
}

旋转数组的最小数字

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。

解题思路:将数组对半分可以得到一个包含最小元素的新旋转数组和一个非递减的数组,利用二分:

  1. 取中间数字,若中间数大于等于第一个数,则最小数位于数组后半部分;
  2. 若中间数字小于等于最后一个数,则最小值在数组前半部分;
  3. 可能出现数组第一个数等于中间数等于末尾数,上述判断条件失效,只能暴力遍历;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if(array.length==0)
return 0;
int l=0,h=array.length-1;
while(l<h){
int m=l+(h-l)/2;
if(array[l]==array[m] && array[m]==array[h]){
for(int i=l;i<h;i++){
if(array[i]>array[i+1])
return array[i+1];
}
return array[l];
}
else if(array[m]<=array[h])
h=m;
else
l=m+1;
}
return array[l];
}
}

调整数组顺序使奇数位于偶数前

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解题思路:类似插入排序,用一个变量lastOdd记录奇数列表的最后一个元素,往后找到每个奇数,都插到lastOdd的后面,然后lastOdd后移一位;

public class Solution {
public void reOrderArray(int [] array) {
int len = array.length;
int lastOdd =0;
for(int i=0;i<len;i++){
if((array[i]&1)==1){
int tmp = array[i];
int ti = i;
for(;ti>lastOdd;ti--){
array[ti] = array[ti-1];
}
array[lastOdd]=tmp;
lastOdd++;
}
}
}
}

链表中倒数第k个结点

题目描述

输入一个链表,输出该链表的倒数k个节点

解题思路:用一个指针p1先在链表上走k-1步(即到第k个节点),然后让p2指向第一个结点,然后让两个指针一起向后移动,直到p1指向最后一个结点;

public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if(head==null || k==0)
return null;
ListNode p1=head;
ListNode p2 = null;
for(int i=0; i<k-1;i++){
if(p1.next!=null){
p1=p1.next;
}else{
return null;
}
}
p2 = head;
while(p1.next!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
}
}

反转链表

方法1:修改每个结点的指针指向前一个结点

public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null || head.next==null)
return head;
ListNode cur = head;
ListNode pre = null;
while(cur.next!=null){
ListNode Next = cur.next;
cur.next = pre;
pre = cur;
cur = Next;
}
cur.next = pre;
return cur;
}
}

方法2:递归

public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null || head.next==null)
return head;
ListNode next = head.next;
head.next = null;
ListNode newHead = ReverseList(next);
next.next = head;
return newHead;
}
}

方法3:头插法

public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode newHead = new ListNode(-1);
while(head!=null){
ListNode tmp = head.next;
head.next = newHead.next;
newHead.next = head;
head = tmp;
}
return newHead.next;
}
}

合并两个排序列表

方法1:递归

public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null)
return list2;
if(list2==null)
return list1;
if(list1.val<=list2.val){
list1.next = Merge(list1.next,list2);
return list1;
}
else{
list2.next = Merge(list2.next,list1);
return list2;
}
}
}

方法2:迭代

public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode newHead = new ListNode(-1);
ListNode cur = newHead;
while(list1!=null && list2!=null){
if(list1.val<=list2.val){
cur.next = list1;
list1 = list1.next;
}
else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(list1!=null)
cur.next = list1;
if(list2!=null)
cur.next = list2;
return newHead.next;
}
}

树的子结构

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

解题思路:

public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if(root1==null || root2==null)
return false;
return isSubTree(root1,root2) || HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}
public static boolean isSubTree(TreeNode r1, TreeNode r2){
if(r2==null)
return true;
if(r1==null)
return false;
if(r1.val!=r2.val)
return false;
return isSubTree(r1.left,r2.left) && isSubTree(r1.right, r2.right);
}
}

二叉树镜像

题目描述

操作给定的二叉树,将其转换为源二叉树的镜像

public class Solution {
public void Mirror(TreeNode root) {
if(root==null)
return;
swap(root);
Mirror(root.left);
Mirror(root.right);
}
public static void swap(TreeNode root){
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
}
}

顺时针打印矩阵

解题思路:模拟

public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> res = new ArrayList<>();
int r1=0, r2=matrix.length-1, c1=0, c2=matrix[0].length-1;
while(r1<=r2 && c1<=c2){
for(int i=c1;i<=c2;i++){
res.add(matrix[r1][i]);
}
for(int i=r1+1; i<=r2; i++){
res.add(matrix[i][c2]);
}
if(r1!=r2){
for(int i=c2-1;i>=c1;i--){
res.add(matrix[r2][i]);
}
}
if(c1!=c2){
for(int i=r2-1;i>r1;i--){
res.add(matrix[i][c1]);
}
}
r1++;r2--;c1++;c2--;
}
return res;
}
}

包含min函数的栈

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

解题思路:额外维护一个附加栈存储当前主栈中的最小元素;

public class Solution {
Stack<Integer> dataStack = new Stack<>();
Stack<Integer> minStack = new Stack<>();
public void push(int node) {
dataStack.push(node);
minStack.push(minStack.isEmpty()?node:Math.min(minStack.peek(),node));
}
public void pop() {
dataStack.pop();
minStack.pop();
}
public int top() {
return dataStack.peek();
}
public int min() {
return minStack.peek();
}
}

栈的压入,弹出序列

题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。

public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> in = new Stack<>();
for(int pushIndex=0,popIndex=0; pushIndex<pushA.length; pushIndex++){
in.push(pushA[pushIndex]);
while(popIndex<popA.length && !in.isEmpty() && in.peek()==popA[popIndex]){
popIndex++;
in.pop();
}
}
return in.isEmpty();
}
}

二叉搜索树的后序遍历序列

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

解题思路:后序遍历的最后一个元素是根结点;遍历数组,找到第一个大于根节点值得元素下标cutIndex,则cutIndex之前得元素都是左子树,cutIndex-end-1的元素都是右子树的结点,判断该部分数值是否都大于根节点,满足则分别对左右数组递归判断,否则返回false;

public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null || sequence.length==0)
return false;
return verify(sequence,0,sequence.length-1);
}
public static boolean verify(int[] seq, int start, int end){
if(end-start<=1)
return true;
int rootval = seq[end];
int cutIndex = start;
while(cutIndex<end && seq[cutIndex]<rootval)
cutIndex++;
for(int i=cutIndex;i<end;i++){
if(seq[i]<rootval)
return false;
}
return verify(seq,start,cutIndex-1) && verify(seq,cutIndex,end-1);
}
}

二叉树中和为某一值得路径

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

解题思路:DFS(先序遍历)+“减法”思想;

注意:

  1. 先序遍历会先遍历左子树,这样会对path进行修改,再去遍历右子树时,如何恢复path到未进行左子树遍历前得状态?一旦遍历到叶子节点,将path得最后一个结点移除,这样递归一层一层进行得时候将值添加进path,在递归返回的过程中将path末尾的元素一个一个移除,完成路径恢复;
  2. 最终遍历到满足条件的叶子节点时,要把path加入结果集,如果直接把path的引用加进去,path会变化,所以要new一个当前路径的副本;
  3. 题目中没有说明节点值非负,所以必须递归到根节点才能确定这条路径能否加出target;
public class Solution {
private ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
backtracking(root,target,new ArrayList<>());
return res;
}
public static void backtracking(TreeNode node, int target, ArrayList<Integer> path){
if(node==null)
return;
path.add(node.val);
target-=node.val;
if(target==0 && node.left==null && node.right==null){
//创建path的副本防止被修改
res.add(new ArrayList<>(path));
}else{
backtracking(node.left,target,path);
backtracking(node.right,target,path);
}
//路径恢复
path.remove(path.size()-1);
}
}

二叉搜索树与双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题思路:

  1. 二叉搜索树中左子节点的值总是小于父节点的值,右子节点的值总是大于父节点的值,因此在转换成双向链表时,原先指向左子结点的指针调整为链表中指向前一个结点的指针,原先指向右子节点的指针调整为链表中指向后一个结点的指针;
  2. 要求转换之后是有序的,二叉搜索树的中序遍历结果是有序的;所以当按照中序递归遍历,当遍历到根节点时,左子树已经转换成一个排序的列表,并且此时链表的尾部值为左子树的最大值,将尾节点和根节点连接起来,这时,根节点为链表尾节点,然后去遍历右子树,将右子树的最小结点(链表头节点)与根节点相连;
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree==null){
return null;
}
if(pRootOfTree.left==null&&pRootOfTree.right==null){
return pRootOfTree;
}
TreeNode left = Convert(pRootOfTree.left);
//找到左子树转化成链表的最后一个结点
TreeNode p = left;
while(p!=null && p.right!=null){
p = p.right;
}
//左子树最后一个结点与根节点连接
if(left!=null){
p.right = pRootOfTree;
pRootOfTree.left = p;
}
//右子树的链表的第一个结点与根结点相连
TreeNode right = Convert(pRootOfTree.right);
if(right!=null){
right.left=pRootOfTree;
pRootOfTree.right = right;
}
//只有一个结点的情形
return left!=null?left:pRootOfTree;
}
}

二叉树的深度

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

public class Solution {
public int TreeDepth(TreeNode root) {
if(root==null)
return 0;
int nLeft = TreeDepth(root.left);
int nRight = TreeDepth(root.right);
return (nLeft>nRight)?(nLeft+1):(nRight+1);
}
}

平衡二叉树

public class Solution {
static boolean isBalance = false;
public boolean IsBalanced_Solution(TreeNode root) {
getDepth(root);
return isBalance;
}
public static int getDepth(TreeNode root){
if(root==null)
return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
int depth = (left > right ? left : right) + 1;
if(Math.abs(left-right)<=1)
isBalance = true;
else
isBalance = false;
return depth;
}
}

二叉树的下一个结点

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解题思路:三种情形,1. 二叉树为空,返回null;2.结点右孩子存在,设置一个指针从右孩子出发一直沿着左子节点的指针找到的叶子节点即为下一个结点;3.结点不是根节点,如果该结点是其父节点的左孩子,则返回父节点,否则向上遍历其父节点的父节点,重复之前的判断;

public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode.right!=null)
{
pNode = pNode.right;
while(pNode.left!=null){
pNode = pNode.left;
}
return pNode;
}
while(pNode.next!=null && pNode.next.right==pNode){
pNode = pNode.next;
}
return pNode.next; }
}

对称的二叉树

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

解题思路:递归判断左右子树是否对称

public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot==null)
return true;
return isMirror(pRoot.left,pRoot.right);
}
static boolean isMirror(TreeNode left, TreeNode right){
if(left==null&&right==null)
return true;
if(left==null||right==null)
return false;
return (left.val==right.val?(isMirror(left.left, right.right)&&isMirror(left.right,right.left)):false);
}
}

二叉搜索树的第k个结点

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

解题思路:二叉搜索树的中序遍历恰好就是排序好的顺序。

public class Solution {
int num=0;
TreeNode p;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot!=null){
KthNode(pRoot.left,k);
num++;
if(num==k)
p=pRoot;
KthNode(pRoot.right,k);
}
return p;
}
}

非递归

TreeNode KthNode(TreeNode root, int k){
if(root==null||k==0)
return null;
Stack<TreeNode> stack = new Stack<TreeNode>();
int count = 0;
TreeNode node = root;
do{
if(node!=null){
stack.push(node);
node = node.left;
}else{
node = stack.pop();
count++;
if(count==k)
return node;
node = node.right;
}
}while(node!=null||!stack.isEmpty());
return null;
}

字符串的排列

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

解题思路:DFS,利用回溯的思想找到一个字符数组的全排列,利用TreeSet完成去重以及字典序排序;

主要方法:先确定第i个字符(i从0-(N-1)进行枚举),对剩下的(i+1)-(N-1)个字符递归使用全排列;需要注意的是,执行完一次dfs过程以后,要将数组复原,便于确定i位置的其他元素时不会受到影响;

import java.util.ArrayList;
import java.util.TreeSet;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> result = new ArrayList<String>();
if(str==null||str.length()==0)
return result;
char[] chars = str.toCharArray();
TreeSet<String> res = new TreeSet<String>();
getRes(chars,0,str.length()-1,res);
result.addAll(res);
return result;
}
static void getRes(char[] chars,int start, int end, TreeSet<String> res){
if(start==end){
res.add(String.valueOf(chars)); }else{
for(int i=start;i<=end;i++){
swap(chars,start,i);
getRes(chars,start+1,end,res);
swap(chars,start,i);
}
}
}
static void swap(char[] chars,int a,int b){
if(a!=b){
char temp = chars[a];
chars[a] = chars[b];
chars[b] = temp;
}
}
}

最小的k个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。

方法1:使用大顶堆来维护最小堆,添加一个元素后,如果大顶堆的大小大于该元素值,那么需要将大顶堆的替换为新的值,重新调整堆;

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
int len = input.length;
if(len<k || k<=0)
return new ArrayList<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k,new Comparator<Integer>(){
public int compare(Integer o1, Integer o2){
return o2.compareTo(o1);
}
});
for(int i=0; i<len; i++){
if(maxHeap.size()<k)
maxHeap.offer(input[i]);
else if(maxHeap.peek()>input[i]){
Integer tmp = maxHeap.poll();
tmp = null;
maxHeap.offer(input[i]);
}
}
return new ArrayList(maxHeap);
}
}

方法2:快速排序的partition方法返回的整数index使得a[index]大于它前面的元素且小于它后面的元素,即第index大的元素,可以利用这个特性找出第k大的元素;

import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if(k>input.length || k<=0)
return res;
findKth(input, k-1);
for(int i=0; i<k; i++)
res.add(input[i]);
return res;
}
public static void findKth(int[] nums, int k){
int l=0, h=nums.length-1;
while(l<h){
int j = partition(nums,l,h);
if(j==k)
break;
if(j>k)
h=j-1;
else
l=j+1;
}
}
public static int partition(int[] nums, int l, int h){
int p = nums[l];
int i=l, j=h+1;
while(true){
while(i!=h && nums[++i]<p);
while(j!=l && nums[--j]>p);
if(j<=i)
break;
swap(nums, i, j);
}
swap(nums,l,j);
return j;
}
public static void swap(int[] nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}

从1-n的整数中1出现的次数

题目描述

求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

解题思路:

设定整数点(如1,10,100等)作为位置点i,分别对每个数位上有多少包含1的点进行分析:

  1. 根据设定的整数位置,对n进行分割,分为两部分,a表示高n/i位,b表示低位n%i;
  2. 以i表示百位为例:

a. 若百位对应的数字大于等于2,如=31456,i=100,则a=314,b=56,此时百位为1的次数为a/10+1=32(最高两位为0-31),每一次都包含100个连续的点,即有(a/10+1)*100个数字百位为1;

b. 若百位对应的数字为1,则共有a/10次是包含100个连续点,再加上局部点b+1,既有(a/10*100+b+1)个数字百位为1;

c. 若百位对应的数字为0,则百位为1的数字个数为a/10*10;

总结:当百位对应0或>=2时,有(a+8)/10次包含所有100个点,当百位为1,需要增加局部点b+1;

public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int result = 0;
for(long m=1; m<=n; m*=10){
result+=((n/m+8)/10)*m+(n/m%10==1?n%m+1:0);
}
return result;
}
}

把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解题思路:在进行字符的排序和选择上,我们定义一个比较规则:对于s1和s2,如果s1+s2<s2+s1,返回true,即按照使得两个字符串相加较小的次序进行排列;

import java.util.Arrays;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if(numbers == null || numbers.length == 0) {
return "";
}
String[] s = new String[numbers.length];
for(int i=0;i<numbers.length;i++)
s[i] = String.valueOf(numbers[i]);
Arrays.sort(s,new Comparator<String>(){
public int compare(String s1,String s2){
return (s1+s2).compareTo(s2+s1);
}
});
StringBuilder sb = new StringBuilder();
for(int i=0;i<s.length;i++){
sb.append(s[i]);
}
return sb.toString();
}
}

丑数

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解题思路:丑数是可以由一个更小的丑数乘以2,3,5得到,从1开始,依次乘以2,3,5,把结果存起来,但是这样不能保证得到的序列是有序的。每次把最小的丑数放入列表,利用三个指针分别表示三种因子,一开始均指向1,每次在该指针表示的索引处的元素乘以对应的因子数,将得到的最小的数存入列表,并将对应的指针后移一位,直到得到第N个丑数;

public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=0)
return 0;
int[] result = new int[index];
result[0]=1;
int p2=0;
int p3=0;
int p5=0;
for(int i=1;i<index;i++){
int min = myMin(result[p2]*2,result[p3]*3,result[p5]*5);
result[i]=min;
if(result[p2]*2<=min)
p2++;
if(result[p3]*3<=min)
p3++;
if(result[p5]*5<=min)
p5++;
}
return result[index-1];
}
static int myMin(int a, int b, int c){
int k = (a<b?a:b);
return k<c?k:c;
}
}

数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

解题思路:先把数组分解为长度为1的子数组接下来合并相邻的子数组,并统计逆序对的数量。

合并子数组并统计逆序对的数量:

  1. 先用两个指针分别指向两个子数组的末尾,并且每次比较两个指针指向的数字:如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数,否则不构成逆序对;
  2. 每一次比较的时候都将较大的数字从后往前复制到辅助数组中,确保辅助数组中数组递增;
public class Solution {
public int InversePairs(int [] array) {
if(array == null || array.length<=1)
return 0;
int[] copy = array.clone();
int result = CountInverse(array, copy, 0, array.length-1);
return result;
}
public static int CountInverse(int[] array,int[] copy,int start,int end){
if(start == end){
copy[start]=array[start];
return 0;
}
int mid = (end-start)/2;
int left = CountInverse(copy,array,start,start+mid);
int right = CountInverse(copy,array,start+mid+1,end);
int i = start+mid;
int j = end;
int index = end;
int count = 0;
while(i>=start && j>=start+mid+1){
if(array[i]>array[j]){
copy[index--]=array[i--];
count+=j-start-mid;
count = count%1000000007;
}
else{
copy[index--]=array[j--];
}
}
for(;i>=start;i--)
copy[index--] = array[i];
for(;j>=start+mid+1;j--)
copy[index--]=array[j];
return (left+right+count)%1000000007;
}
}

和为s的连续正数序列

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

解题思路:用两个变量start,end分别表示连续正数序列的起点和终点,并且根据和为S可以得出start小于(1+S)/2;计算start到end的和curSum,如果curSum小于S,end右移(S+end),大于S,start右移(S-start),若S=curSum,将该子序列加入结果集。

import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer> > L = new ArrayList<ArrayList<Integer> >();
if(sum<3)
return L;
int start = 1;
int end = 2;
int mid = (1+sum)/2;
int curSum=start+end;
while(start<mid){
if(curSum==sum)
{
ArrayList<Integer> l = new ArrayList<Integer>();
for(int i=start;i<=end;i++)
l.add(i);
L.add(l);
}
while(curSum>sum && start<mid){
curSum-=start;
start++;
if(curSum==sum){
ArrayList<Integer> l = new ArrayList<Integer>();
for(int i=start;i<=end;i++)
l.add(i);
L.add(l);
}
}
end++;
curSum+=end;
}
return L;
}
}

孩子们的游戏(圆圈中最后剩下的数)

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1),如果没有小朋友,返回-1.

解题思路:寻找数学规律,

public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n<1 || m<1)
return -1;
int last=0;
for(int i=2;i<=n;i++)
last = (last+m)%i;
return last;
}
}

不用加减乘除法号做加法

题目描述

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

解题思路:十进制加法分三步:1.相加各位的值不进位;2.计算进位值;3.重复上述两个步骤; 二进制的加法也可以分为3步:1.相加个位的值不算进位,相当于各位做异或操作;2.计算进位值,相当于各位做与操作后向左位移一位;3.重复上述两步直到进位值为0;

public class Solution {
public int Add(int num1,int num2) {
int sum,carry;
do{
sum=num1^num2;
carry=(num1&num2)<<1;
num1 = sum;
num2 = carry;
}while(num2!=0);
return num1;
}
}

链表中环的入口结点

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

解题思路:

public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead==null || pHead.next==null)
return null;
ListNode p1 = pHead;
ListNode p2 = pHead;
while(p2!=null && p2.next!=null){
p2 = p2.next.next;
p1 = p1.next;
if(p1==p2){
p2=pHead;
while(p1!=p2){
p1=p1.next;
p2=p2.next;
}
}
if(p1==p2)
return p1;
}
return null;
}
}

滑动窗口的最大值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

解题思路:用一个双端队列,队列第一个位置保存当前窗口的最大值,当窗口滑动一次,判断当前最大值是否过期,新增的值从队尾开始比较,把所有比他小的值丢掉

import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> result = new ArrayList<Integer>();
if(num==null||num.length<=0||size<=0)
return result;
int begin;
ArrayDeque<Integer> q = new ArrayDeque<Integer>();
for(int i=0;i<num.length;i++){
begin=i-size+1;
if(q.isEmpty())
q.add(i);
else if(begin>q.peekFirst())
q.pollFirst();
while((!q.isEmpty())&&num[q.peekLast()]<=num[i])
q.pollLast();
q.add(i);
if(begin>=0)
result.add(num[q.peekFirst()]);
}
return result;
}
}

数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

解题思路:

  1. 构造一个最大堆和最小堆,最大堆用来存放较小的那一半数据,最小堆用来存放较大的那一半数据,每次插入数据时:
    1. 当数据总数为偶数时,新加入的元素进入大根堆(经小根堆筛选后取小根堆中最小元素进入大根堆);
    2. 当数据总数为奇数时,新加入的元素进入小根堆(经大根堆筛选后取大根堆中最大元素进入小根堆)
    3. 求中位数时,如果数据总数为偶数时,取两个堆堆顶元素的均值;否则,取大根堆的堆顶元素
public class Solution {
public PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
public PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new Comparator<Integer>(){
public int compare(Integer a,Integer b){
return b-a;
}
});
int len=0;
public void Insert(Integer num) {
if(len%2==0){
minHeap.offer(num);
maxHeap.offer(minHeap.poll());
}
else{
maxHeap.offer(num);
minHeap.offer(maxHeap.poll());
}
len++;
} public Double GetMedian() {
if(len%2==0)
return Double.valueOf((minHeap.peek()+maxHeap.peek())/2.0);
else
return Double.valueOf(maxHeap.peek());
}
}

机器人的运动范围

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

解题思路:回溯

  1. 从起点开始,每成功走一步标记当前位置为true然后从当前位置向四个方向探索,最后返回1+四个方向的探索之和;
  2. 探索时,判断当前结点是否可达的标准为:
    1. 当前结点在矩阵内;
    2. 当前结点未被访问过;
    3. 当前结点满足行列坐标位数之和小于等于k
public class Solution {
public int movingCount(int threshold, int rows, int cols)
{
if(threshold < 0 || rows <= 0 || cols <= 0)
return 0;
boolean[] visited = new boolean[rows*cols];
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++)
visited[i*cols+j] = false;
}
int count = movingCountCore(threshold, rows, cols, 0, 0, visited);
return count;
}
static int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] visited){
int count = 0;
if(check(threshold, rows, cols, row, col, visited)){
visited[row*cols+col] = true;
count+=1+movingCountCore(threshold,rows,cols,row+1,col,visited)+
movingCountCore(threshold,rows,cols,row,col+1,visited)+
movingCountCore(threshold,rows,cols,row-1,col,visited)+
movingCountCore(threshold,rows,cols,row,col-1,visited);
}
return count;
}
static boolean check(int threshold, int rows,int cols,int row,int col,boolean[] visited){
if(row>=0 && row<rows && col>=0 && col<cols && getSum(row)+getSum(col)<=threshold && !visited[row*cols+col])
return true;
return false;
}
static int getSum(int a){
int sum=0;
while(a>0){
int tmp = a%10;
sum+=tmp;
a=a/10;
}
return sum;
}
}

矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bccced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

解题思路:回溯法

  1. 遍历这个矩阵,找到与字符串str中第一个字符相同的矩阵元素,然后遍历该元素的上下左右四个字符,如果有和字符串str中下一个字符相同的就把那个字符作为下一次遍历的起点,如果没有则回退到上一个字符然后重新遍历。为了避免路径重叠,需要一个辅助矩阵来记录路径情况,且回退到上一个结点时,要把访问记录擦除;
public class Solution {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
int[] visited = new int[matrix.length];
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(helper(matrix,rows,cols,i,j,str,0,visited)){
return true;
}
}
}
return false;
}
public static boolean helper(char[] matrix, int rows, int cols, int i, int j, char[] str, int k, int[] vis){
int index = i*cols+j;
if(i<0 || i>=rows ||j<0 || j>=cols || matrix[index]!=str[k]||vis[index]==1)
return false;
if(k==str.length-1)
return true;
vis[index]=1;
if(helper(matrix,rows,cols,i-1,j,str,k+1,vis)
||helper(matrix,rows,cols,i+1,j,str,k+1,vis)
||helper(matrix,rows,cols,i,j-1,str,k+1,vis)
||helper(matrix,rows,cols,i,j+1,str,k+1,vis))
return true;
vis[index]=0;
return false;
}
}