剑指《offer》

《剑指offer》

第1题 数组:二维数组中的查找

  1. 题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  2. 解法:
    (1)暴力法:时间复杂度(O(n2)) 空间复杂度(O(1))
public class Solution {
    public boolean Find(int target, int [][] array) {
        for(int i=0;i<array.length;i++){
            for(int j=0;j<array[0].length;j++){
                if(target == array[i][j]){
                    return true;
                }
            }
        }
        return false;
      }
   }

(2)右上法:时间复杂度(O(行数+列数)) 空间复杂度(O(1))
思路:如果大于右上则行加加,如果小于有上则列减减。

public class Solution {
    public boolean Find(int target, int [][] array) {
        int row = 0;
        int col = array[0].length-1;
        while(col >= 0 && row <= array.length-1 ){
            if(target == array[row][col]){
                return true;
            }else if(target < array[row][col]){
                col--;
            }else if(target > array[row][col]){
                row++;
            }
        }
        return false;
    }
}

(3)左下法:时间复杂度(O(行数+列数)) 空间复杂度(O(1))
思路:如果大于左下则列加价,如果小于左下则行减减。

public class Solution {
    public boolean Find(int target, int [][] array) {
        int row = array.length-1;
        int col = 0;
        while(col <= array[0].length-1 && row >= 0){
            if(target == array[row][col]){
                return true;
            }else if(target < array[row][col]){
                row--;
            }else if(target > array[row][col]){
                col++;
            }
        }
        return false;
    }
}

(4)二分查找法:时间复杂度(O(nlog(n))) 空间复杂度(O(1))
思路:利用数组从左到右递增,从上到下递增。有序性。

public class Solution {
    public boolean Find(int target, int [][] array) {
       for(int i=0 ; i < array.length ; i++){
           int low = 0; 
           int high = array[0].length - 1;
           int mid;
           while(low <= high){
               mid = (low + high)/2;
               if(target == array[i][mid]){
                   return true;
               }else if(target < array[i][mid]){
                   high = mid -1;
               }else {
                   low = mid + 1;
               }
           }
       }
       return false;
    }
}

第2题 字符串:替换空格

  1. 题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
  2. 解法:
    问题1:替换字符串,是在原来的字符串上做替换,还是新开辟一个字符串做替换!
    问题2:在当前字符串替换,怎么替换才更有效率(不考虑java里现有的replace方法)。
  • 从前往后替换,后面的字符要不断往后移动,要多次移动,所以效率低下。
  • 从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次,这样效率更高一点。从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
    (1)开辟新的字符串,时间复杂度o(n),空间复杂度o(n).
    (2)不开辟新的字符串,时间复杂度o(2n),空间复杂度o(1).
public class Solution {
    public String replaceSpace(StringBuffer str) {
        String str1 = new String();
        if(str==null){
            return str1; 
        }
        char[] chars = str.toString().toCharArray();
        for(int i=0;i<chars.length;i++){
            if(chars[i] == ' '){
                str1 = str1+"%20";
            }else{
                str1 = str1+chars[i];
            }
        }
        return str1;
    }
}
public class Solution {
    public String replaceSpace(StringBuffer str) {
        if(str==null){
            return null; 
        }
        StringBuffer res = new StringBuffer();
        for(int i=0;i<str.length();i++){
            if(str.charAt(i) == ' '){
                res.append("%20");
            }else{
                res.append(str.charAt(i));
            }
        }
        return res.toString();
    }
}
public class Solution {
    public String replaceSpace(StringBuffer str) {
        int spacenum = 0;//spacenum为计算空格数
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' ')
                spacenum++;
        }
        int indexold = str.length()-1; //indexold为为替换前的str下标
        int newlength = str.length() + spacenum*2;//计算空格转换成%20之后的str长度
        int indexnew = newlength-1;//indexold为为把空格替换为%20后的str下标
        str.setLength(newlength);//使str的长度扩大到转换成%20之后的长度,防止下标越界
        for(;indexold>=0 && indexold<newlength;--indexold){ 
                if(str.charAt(indexold) == ' '){  //
                    str.setCharAt(indexnew--, '0');
                    str.setCharAt(indexnew--, '2');
                    str.setCharAt(indexnew--, '%');
                }else{
                    str.setCharAt(indexnew--, str.charAt(indexold));
                }
        }
        return str.toString();
    }
}

第3题 链表:从尾到头打印链表

  1. 题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
  2. 解法:
    (1)利用栈先进后出的特性。
    (2)递归。
    (3)数组翻转。
/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        if(listNode == null){
            return list;
        }
        Stack<Integer> stack = new Stack<>();
        ListNode node = listNode;
        while(node != null){
            stack.push(node.val);
            node = node.next;
        }
        while(!stack.isEmpty()){
            list.add(stack.pop());
        }
        return list;
    }
}
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        if(listNode!=null){
            list.addAll(printListFromTailToHead(listNode.next));
            list.add(listNode.val);
        }
        return list;
    }
}
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        while(listNode!=null){
            list.add(listNode.val);
            listNode = listNode.next;
        }
        //数组翻转
        int i=0;
        int j=list.size()-1;
        while(i<j){
            int temp = list.get(i);
            list.set(i,list.get(j));
            list.set(j,temp);
            i++;
            j--;
        }
        return list;
    }
}

第4题 二叉树:重建二叉树

  1. 题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
  2. 解法:树的解题一般使用递归。
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        //数组长度为0时的处理
        if(pre.length == 0){
            return null;
        }
        //先序遍历的特性,第一个即是树的根节点
        TreeNode root = new TreeNode(pre[0]);
        //数组长度为1时的处理
        if(pre.length == 1){
            return root;
        }
        //数组长度不是大于1时,找根节点和左右节点
        int index = 0;
        //查找根节点在中序遍历中的位置,
        //在该位置之前全部是左子树,在该位置之后全部是右子树
        while(in[index] != root.val){
            index++;
        }
        //利用递归寻找根节点的左节点和右节点
        root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,index+1),
                Arrays.copyOfRange(in,0,index));
        root.right = reConstructBinaryTree(Arrays.copyOfRange(pre,index+1,
                pre.length), Arrays.copyOfRange(in,index+1,in.length));
        //返回根节点
        return root;
    }
}

第5题 栈:用两个栈实现队列

  1. 题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
  2. 解法:
import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();//存放数据
    Stack<Integer> stack2 = new Stack<Integer>();//交换数据
    //队列是先进先出,栈是先进后出,让每一次push进栈的栈底而不是栈顶。
    //每一次pop从栈的栈顶pop即可
    //这种方法需要Stack1出栈和进栈,效率不是太高。
    public void push(int node) {
        if(stack1.size() == 0){
            stack1.push(node);
        }else{
            while(stack1.size() != 0){
                stack2.push(stack1.pop());
            }
            stack1.push(node);
            while(stack2.size() != 0){
                stack1.push(stack2.pop());
            }
        }
    }
    public int pop() {
        if(stack1.size() == 0){
            return (Integer)null;
        }else{
            return stack1.pop();
        }
    }
}
import java.util.Stack;
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() {
        if (out.isEmpty())
            while (!in.isEmpty())
                out.push(in.pop());
        if (out.isEmpty())
            throw new Exception("queue is empty");
        return out.pop();
    }
}

第6题 数组查找:旋转数组的最小数字

  1. 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
    输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
    例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
    NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
  2. 解法:
    (1)考虑到最小值前面的数组和最小值后面的数组都是递增的,而最小值的位置就是,最小值小于前面的值。时间复杂度(O(n))。
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array == null || array.length == 0){
            return 0;
        }
        if(array.length==1){
            return array[0];
        }
        int i=0;
        for(;i<array.length-1;i++){
            if(array[i]>array[i+1]){
                return array[i+1];
            }
        }
        if(i==array.length-2){
            return array[0];
        }
        return 0;
    }
}

(2)使用二分查找,时间复杂度(O(logn))。

采用二分法解答这个问题,
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]
array[low] = 4 ;array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误
import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0){
            return 0;
        }
        //考察查找算法;由于旋转数组的两部分都是有序的所以可以使用二分查找的方法
        int low = 0;
        int high = array.length-1;
        int mid;
        while(low < high){//最后low在第一个序列的最后一个,high在第二个序列的第一个
            mid = (int)(low + high)/2;
            if(array[mid] > array[high]){//最小值在mid的右边[3,4,5,6,0,1,2]
                low = mid+1;
            }else if(array[mid] == array[high]){// [1,0,1,1,1] 或者[1,1,1,0,1]
                high = high - 1;
            }else{//[2,2,3,4,5,6,6]    [4,6]
                high = mid;
            }
        }
        return array[low];
    }
}

第7题 递归:斐波那契数列

  1. 题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39。
    2.考点:递归VS动态规划。
  2. 解法:
    (1)使用递归,运行内存开销大。如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。
    在这里插入图片描述
    (2)使用循环,不使用递归。递归是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,从而避免重复求解子问题。【空间复杂度o(n+1)】
    (3)考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。
    (4)由于待求解的 n 小于 40,因此可以将前 40 项的结果先进行计算,之后就能以 O(1) 时间复杂度得到第 n 项的值。
public class Solution {
    public int Fibonacci(int n) {
        if(n == 0){
            return 0;
        }else if(n == 1){
            return 1;
        }else{
            return (Fibonacci(n-1)+Fibonacci(n-2));
        }
    }
}
public class Solution {
    public int Fibonacci(int n) {
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        int[] arr = new int[n+1];
        arr[1] = 1;
        if(n>=2){
             for(int i=2;i<=n;i++){
                arr[i] = arr[i-1]+arr[i-2];
            }
        }
        return arr[n];
    }
}
public class Solution {
    public int Fibonacci(int n) {
        int pre1 = 0;
        int pre2 = 1;
        int res = 0;
        if(n == 0){
            return 0;
        }else if(n == 1){
            return 1;
        }else{
            for (int i=2; i<=n;i++){
                res = pre1 + pre2;
                pre1 = pre2;
                pre2 = res;
            }
            return res;
        }
    }
}
public class Solution {
    int[] arr = new int[40];  
    public int Fibonacci(int n) {
        arr[1] = 1;
        if(n>=2){
             for(int i=2;i<40;i++){
                arr[i] = arr[i-1]+arr[i-2];
            }
        }
        return arr[n];
    }
}

第8题 递归:跳台阶

  1. 题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
  2. 考点:递归。
  3. 解法:
    (1)使用递归:n=0时,为0;n=1时,为1;n=2时,为2;当n>2时,为fib(n-2)+fib(n-1)。编程了斐波那契数列。
    (2)使用循环代替递归。
public class Solution {
    public int JumpFloor(int target) {
        if(target == 0){
            return 0;
        }else if(target == 1){
            return 1;
        }else if(target == 2){
            return 2;
        }else{
            return (JumpFloor(target-2)+JumpFloor(target-1));
        }
    }
}
public class Solution {//最好的做法
    public int JumpFloor(int target) {
        int pre1 = 1;
        int pre2 = 2;
        int res = 0;
        if(target == 0){
            return 0;
        }else if(target == 1){
            return 1;
        }else if(target == 2){
            return 2;
        }else{
            for (int i = 3 ; i<= target ; i++){
                res = pre1 + pre2;
                pre1 = pre2;
                pre2 = res;
            }
            return res;
        }
    }
}

第9题 递归:变态跳台阶

  1. 题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
  2. 考点:贪心算法。
  3. 解法:
    (1)动态规划。fib[i] = fib[i-1]+fib[i-2]…+fib[i-i]
    (2)数学推倒:fib[i-1] = fib[i-2]+…+fib[i-i], fib[i] = 2fib[i-1] //变成了等比数列 2^(target-1)
import java.util.Arrays;
public class Solution {
    public int JumpFloorII(int target) {
        int[] arr = new int[target];
        Arrays.fill(arr,1);
        for(int i=1 ; i < target ; i++){
            for(int j=0 ; j < i ; j++){
                arr[i] += arr[j];
            }
        }
        return arr[target-1];
    }
}
import java.util.Arrays;
public class Solution {
    public int JumpFloorII(int target) { 
        return (int)Math.pow(2,target-1);
        //  int a=1; return a<<(number-1);
    }
}

第10题 递归:矩形覆盖

  1. 题目:我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
  2. 考点:递归。
  3. 解法:当n=1时,只有一种覆盖方式;当n=2时,有两种覆盖方式;当n>2时,先覆盖21的矩形,再覆盖2n-1的矩形,fib(n-1),或者先覆盖22的矩形,再覆盖2n-2的矩形,fib(n-2)。
    (1)使用递归。
    (2)使用循环替代递归。
public class Solution {
    public int RectCover(int target) {
        if(target == 1){
            return 1;
        }else if(target == 2){
            return 2;
        }else if(target > 2){
            return RectCover(target-1)+RectCover(target-2);
        }else{
            return 0;
        }
    }
}
public class Solution {
    public int RectCover(int target) {
        int pre1 = 1;
        int pre2 = 2;
        int res = 0;
        if(target == 1){
            return 1;
        }else if(target == 2){
            return 2;
        }else{
            for(int i=2 ; i<target ; i++){
                res = pre1 + pre2;
                pre1 = pre2;
                pre2 = res;
            }
            return res;
        }
    }
}

第11题 进制转换:二进制中1的个数

1.题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
2.考点:进制转化,补码反码原理。
3.解法:
常识1:在计算机系统中,数值一律用补码来表示和存储。
常识2:正数的原码、反码、补码都是其本身。
(1)容易理解,但效率不高,因根据flag判断循环结束条件,因此每判断一个数都要循环32次。
(2)效率更高,但是不容易理解,可以看排行第二的大佬 “菩提旭光” 的解释,很透彻。

  • 该方法只需要循环输入整数二进制中1的个数次,相较方法一有了很大提升。如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
  • 举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

(3)Integer.bitCount()。bitCount实现的功能是计算一个(byte,short,char,int统一按照int方法计算)int,ong类型的数值在二进制下“1”的数量。

public class Solution {
    public int NumberOf1(int n) {
        int flag = 1;
        int count = 0;
        while(flag != 0){
            if((n & flag) != 0){
                count++;
            }
            flag = flag<<1;
        }
        return count;
    }
}
//1100 - 0001 = 1011 最右边的1变为0,最右边1后面的0变为1
//1100 & 1011 = 1000 最有边的1变为0 
//基于此判断二进制中1的个数

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while (n!=0){
            count++;
            n = n&(n-1);
        }
        return count;
    }
}
public class Solution {
    public int NumberOf1(int n) {
        return Integer.bitCount(n);
    }
}
public class Solution {
    //从n的2进制形式的最右边开始判断是不是1
    /*
    * 该解法如果输入时负数会陷入死循环,
    * 因为负数右移时,在最高位补得是1
    * 二本题最终目的是求1的个数,那么会有无数个
    * 1了。
    */
    //-------------可能陷入死循环的解法---------------------
    public static int NumberOf1_CanNotUse(int n) {
        int count = 0;
        while (n != 0) {
            /*
            * 用1和n进行位与运算,
            * 结果要是为1则n的2进制形式
            * 最右边那位肯定是1,否则为0
            */
            if ((n & 1) == 1) {
                count++;
            }
            //把n的2进制形式往右推一位
            n = n >> 1;
        }
        return count;
    }
    
    //---------------正解--------------------------------
    //思想:用1(1自身左移运算,其实后来就不是1了)和n的每位进行位与,来判断1的个数
    private static int NumberOf1_low(int n) {
        int count = 0;
        int flag = 1;
        while (flag != 0) {
            if ((n & flag) != 0) {
                count++;
            }
            flag = flag << 1;
        }
        return count;
    }
    
    //--------------------最优解----------------------------
    public static int NumberOf1(int n) {
        int count = 0;
        while (n != 0) {
            ++count;
            n = (n - 1) & n;
        }
        return count;
    }
    public static void main(String[] args) {
        //使用n=10,二进制形式为1010,则1的个数为2;
        int n = -10;
        System.out.println(n + "的二进制中1的个数:" + NumberOf1(n));
    }
}

第12题 数学:数值的整数次方

1.题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0。
2.考点:数学,全面考察指数。
3.解法:把所有的情况都考虑在内。
(1)传统公式求解,时间复杂度(O(n))。
(2)考虑奇数偶数(O(logn))。递归:
在这里插入图片描述
(3)快速幂算法/** * 1.全面考察指数的正负、底数是否为零等情况。 * 2.写出指数的二进制表达,例如13表达为二进制1101。 * 3.举例:10^1101 = 100001*100100*10^1000。 * 4.通过&1和>>1来逐位读取1101,为1时将该位代表的乘数累乘到最终结果。 */【时间复杂度o(32)int类型的长度】

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent == 0 && base != 0){
            return 1;
        }
        if(exponent == 1){
            return base;
        }
        if(base == 0 && exponent <= 0 ){
            throw new RuntimeException();
        }
        if(base == 0 && exponent > 0){
            return 0;
        }
        //exponent != 0 ,base != 0
        int n = Math.abs(exponent);
        double rest = Power(base*base,n/2);
        if(n%2 == 1){
            rest *= base;
        }
        if(exponent < 0){
            rest = 1/rest;
        }
        return rest;
  }
}
public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0 && exponent> 0){
            return 0;
        } 
        if(base==0 && exponent<=0){
            throw new RuntimeException();
        }
        if(exponent == 0 && base!=0){
            return 1;
        }
        if(exponent == 1){
            return base;
        }
        boolean flag = false;
        if(exponent<0){
            flag = true;
            exponent = -exponent;
        }
        double res = 1;
        double cur = base;
        while(exponent != 0){
            if((exponent&1) !=0){
                res = res*cur;
            }
            cur = cur*cur;
            exponent = exponent>>1;
        }
        return flag ? 1 / res : res;
    }
}

第13题 数组:调整数组顺序使奇数位于偶数前面

1.题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
2.考点:数组。
3.解法:
(1)构建新数组。时间复杂度 O(N),空间复杂度O(N)。
(2)使用冒泡排序。时间负责度O(N2),空间复杂度O(1)。每次都当前偶数上浮到当前最右边。
(3)/** * 1.要想保证原有次序,则只能顺次移动或相邻交换。 * 2.i从左向右遍历,找到第一个偶数。 * 3.j从i+1开始向后找,直到找到第一个奇数。* 4.将[i,…,j-1]的元素整体后移一位,最后将找到的奇数放入i位置,然后i++。 * 5.終止條件:j向後遍歷查找失敗。 */
(4)插入排序:

//123456 132456 132546 135246
public class Solution {
    public void reOrderArray(int [] array) {
        //创建一个新数组
        int[] newArr = new int[array.length];
        int count =0;
        for(int i=0;i<array.length;i++){
            if(array[i]%2 != 0){
                newArr[count++] = array[i];
            }
        }
        for(int i=0;i<array.length;i++){
            if(array[i]%2 == 0){
                newArr[count++] = array[i];
            }
        }
        for(int i=0;i<array.length;i++){
            array[i] = newArr[i];
        }
    }
}
public class Solution {
    public void reOrderArray(int [] array) {
        //创建一个新数组
        int N = array.length;
        for(int i=0 ; i < N ; i++){
            for(int j = 0 ; j < N-i-1 ; j++){
                if(isEven(array[j]) && !isEven(array[j+1])){
                    swap(array,j,j+1);
                }
            }
        }
    }
    public static boolean isEven(int num){
        return num%2 == 0;
    }
    public static void swap(int[] array , int i ,int j){
        int z = array[i];
        array[i]= array[j];
        array[j] = z;
    }
}
public class Solution {
    public void reOrderArray(int [] array) {
        if(array==null || array.length==0){
            return ;
        }
        int length = array.length;
        int i=0;
        int j;
        while(i<length){
            while(i<length && array[i]%2!=0){
                i++;
            }
            j =i+1;
            while(j<length && array[j]%2==0){
                j++;
            }
            if(j<length){
                int temp = array[j];
                j--;
                while(j>=i){
                    array[j+1] = array[j];
                    j--;
                }
                array[i] = temp;
                i++;
            }else{
                break;
            }
        }
    }
}
public class Solution {
    public void reOrderArray(int [] array) {
        if(array==null || array.length==0){
            return ;
        }
        int length = array.length;
        for(int i=0;i<length;i++){
            if(array[i]%2 == 1){
                int temp = array[i];
                int j = i-1;
                while(j>=0 && array[j]%2==0){
                    array[j+1] = array[j];
                    j--;
                }
                array[j+1] = temp;
            }
        }
        
    }
}

第14题 链表:链表中倒数第k个节点

1.题目:输入一个链表,输出该链表中倒数第k个结点。
2.考点:链表的查找。
3.解法:设链表的长度为 N。设置两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到第 N - K 个节点处,该位置就是倒数第 K 个节点。时间复杂度(o(n))。
在这里插入图片描述

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        //倒数第k个,正数第N-k个
        if(head == null){
            return null;
        }
        ListNode node1 = head;
        ListNode node2 = head;
        while(k>0 && node1 != null){
            node1 = node1.next;
            k--;
        }
        if(k > 0){
            return null;
        }
        while(node1 != null){
            node1 = node1.next;
            node2 = node2.next;
        }
        return node2;
    }
}

第15题 链表:反转链表

1.题目:输入一个链表,反转链表后,输出新链表的表头。
2.考点:链表反转。
3.解法:
(1)递归法。
在这里插入图片描述
(2)头插法;修改指针。循环法。

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null){
            return null;
        }
        if(head.next == null){
            return head;
        }
        ListNode node = ReverseList(head.next);
        head.next.next = head;
        head.next = null;
        return node;
    }
}
/*
public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null){
            return null;
        }
        ListNode node = head;
        ListNode next = null;
        ListNode pre = null;
        while(node != null){
            next = node.next;
            node.next = pre;
            pre = node;
            node = next;
        }
        return pre;
    }
}

第16题 链表:合并两个排序的链表

1.题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
2.考点:链表
3.解法:迭代。或者递归。

//递归
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(list1,list2.next);
            return list2;
        }
    }
}
//迭代
/*
public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        //新建一个头结点,用于存储新合并的链表
        ListNode head = new ListNode(-1);
        head.next = null;
        ListNode cur = head;
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                cur.next = list1;
                cur = list1;
                list1 = list1.next;
            }else{
                cur.next = list2;
                cur = list2;
                list2 = list2.next;
            }
            //cur = cur.next;
        }
        if(list1 != null){
            cur.next = list1;
        }
        if(list2 != null){
            cur.next = list2;
        }
        return head.next;
    }
}    

第17题 树:树的子结构

1.题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
2.考点:二叉树。
3.解法:递归。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
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 boolean isSubtree(TreeNode root1,TreeNode root2){
        if(root2 == null){
            return true;
        }
        if(root1 == null){
            return false;
        }
        if(root1.val != root2.val){
            return false;
        }
        return isSubtree(root1.left,root2.left) && isSubtree(root1.right,root2.right);
    }
}
public class Solution {
    public static boolean HasSubtree(TreeNode root1, TreeNode root2) {
        boolean result = false;
        //当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
        if (root2 != null && root1 != null) {
            //如果找到了对应Tree2的根节点的点
            if(root1.val == root2.val){
                //以这个根节点为为起点判断是否包含Tree2
                result = doesTree1HaveTree2(root1,root2);
            }
            //如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
            if (!result) {
                result = HasSubtree(root1.left,root2);
            }
            //如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
            if (!result) {
                result = HasSubtree(root1.right,root2);
               }
            }
            //返回结果
        return result;
    }
    public static boolean doesTree1HaveTree2(TreeNode node1, TreeNode node2) {
        //如果Tree2已经遍历完了都能对应的上,返回true
        if (node2 == null) {
            return true;
        }
        //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
        if (node1 == null) {
            return false;
        }
        //如果其中有一个点没有对应上,返回false
        if (node1.val != node2.val) {  
                return false;
        }
        //如果根节点对应的上,那么就分别去子节点里面匹配
        return doesTree1HaveTree2(node1.left,node2.left) && 
        		doesTree1HaveTree2(node1.right,node2.right);
    }
}

第18题 二叉树:二叉树的镜像

1.题目:操作给定的二叉树,将其变换为源二叉树的镜像。
在这里插入图片描述
2.考点:二叉树。
3.解法:
(1)递归实现。/* 先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子节点,当交换完所有的非叶子结点的左右子结点之后,就得到了树的镜像 */
(2)非递归,层次遍历,使用队列或者栈。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return ;
        }
        swap(root);
        Mirror(root.left);
        Mirror(root.right);
    }
    private void swap(TreeNode root){
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
    }
}
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            TreeNode temp = node.left;
            node.left = node.right;
            node.right = temp;
            if(node.left!=null)
                queue.add(node.left);
            if(node.right!=null)
                queue.add(node.right);
        }
    }
}

第19题 数组:顺时针打印矩阵

1.题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
2.考点:数组。
3.解法:顺时针打印就是按圈数循环打印,一圈包含两行或者两列,在打印的时候会出现某一圈中只包含一行,要判断从左向右打印和从右向左打印的时候是否会出现重复打印,同样只包含一列时,要判断从上向下打印和从下向上打印的时候是否会出现重复打印的情况。

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

第20题 栈:包含min函数的栈

1.题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
2.考点:栈
3.解法:看到这个问题, 我们最开始可能会想, 添加一个成员变量用于保存最小元素, 每次压栈时如果压栈元素比当前最小元素更小, 就更新最小元素. 但是这样会有一个问题, 如果最小元素被弹出了呢, 如何获得下一个最小元素呢? 分析到这里可以发现, 仅仅添加一个成员变量存放最小元素是不够的, 我们需要在最小元素弹出后还能得到次小元素, 次小的弹出后, 还要能得到次次小的. 因此, 用另一个栈来保存这些元素是再合适不过的了. 我们叫它最小元素栈. 每次压栈操作时, 如果压栈元素比当前最小元素更小, 就把这个元素压入最小元素栈, 原本的最小元素就成了次小元素. 同理, 弹栈时, 如果弹出的元素和最小元素栈的栈顶元素相等, 就把最小元素的栈顶弹出.

import java.util.Stack;
public class Solution {
    Stack<Integer> stack = new Stack<Integer>();
    Stack<Integer> minStack = new Stack<Integer>();
    public void push(int node) {
        if(stack.isEmpty()){
            minStack.push(node);
        }else{
            if(minStack.peek()>node){
                minStack.push(node);
            }else{
                minStack.push(minStack.peek());
            }
        }
        stack.push(node);
    }
    
    public void pop() {
        stack.pop();
        minStack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return minStack.peek();
    }
}

第21题 栈:栈的圧入,弹出序列

1.题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
2.考点:栈。
3.解法:【思路】借用一个辅助的栈,遍历压栈顺序,先将第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈,此时栈顶1≠4,继续入栈2
此时栈顶2≠4,继续入栈3
此时栈顶3≠4,继续入栈4
此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
此时栈顶3≠5,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3

依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。

import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        f(pushA == null || popA == null || pushA.length == 0 
            || popA.length == 0 || pushA.length != popA.length)
             return false;
        int length = pushA.length;
        Stack<Integer> stack = new  Stack<Integer>();
        //i表示圧入栈的位置,j表示弹出序列的位置
        for(int i=0,j=0;i<length;i++){
            stack.push(pushA[i]);
            while(j<length && !stack.isEmpty() && popA[j]==stack.peek()){
                //栈顶出栈
                stack.pop();
                //弹出序列后移一位
                j++;
            }
        }
        return stack.isEmpty();
    }
}

第22题 二叉树:从上往下打印二叉树(二叉树按层次遍历)

1.题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
2.考点:打印二叉树。
3.解法:从根节点开始将根节点放入队列,再将根节点出队列,将根节点的左节点入队列右节点入队列,将左节点出队列,将左节点的左右节点入队列,对列头出队列,。。。,直到队列为空。

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root){
        ArrayList<Integer> list = new ArrayList<Integer>();
        //思路是用arraylist模拟一个队列来存储相应的TreeNode
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        if(root == null){
            return list;
        }
        queue.add(root);
        TreeNode node = null;
        while(!queue.isEmpty()){
            node = queue.poll();
            list.add(node.val);
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
        }
        return list;
    }
}

第23题 二叉树:二叉搜索树的后序遍历序列

1.题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
2.考点:栈,树。
3.解法:
(1)二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
(2)使用递归:采用分治法的思想,找到根节点,左子树的序列,右子树的序列,分别判断左右子序列是否为二叉树的后序序列。后序序列的最后一个节点是二叉树的根节点。二叉搜索树左子树上所有节点小于根节点,二叉搜索树右子树上所有节点大于根节点。
(3)二叉树的中序序列和后序序列满足栈的圧入弹出序列关系。即如果把中序序列当做栈的圧入序列,那么后续序列是该栈的一个弹出序列。而BST(二叉搜索树)的中序序列是排序序列。因此将本题中的排序数组作为中序序列,判断原数组与排序数组满足不满足圧入弹出关系。
(4) 非递归也是一个基于递归的思想:左子树一定比右子树小,因此去掉根后,数字分为left,right两部分,right部分的最后一个数字是右子树的根他也比左子树所有值大,因此我们可以每次只看有子树是否符合条件即可,即使到达了左子树左子树也可以看出由左右子树组成的树还想右子树那样处理对于左子树回到了原问题,对于右子树,左子树的所有值都比右子树的根小可以暂时把他看出右子树的左子树只需看看右子树的右子树是否符合要求即可。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence==null || sequence.length==0){
            return false;
        }
        return verify(sequence,0,sequence.length-1);
    }
    private boolean verify(int[] sequence,int start, int end){
        //如果序列个数小于等于2,返回true,一定可以构建后续二叉搜索树
        if(end-start<=1){
            return true;
        }
        //找到右子树的序列开始的节点,同时左子树序列值都小于根节点值
        int index = start;
        while(index < end && sequence[index] <= sequence[end] ){
            index++;
        }
        //判断右子树序列值是否都大于根节点值
        int mid = index;
        while(index+1 < end){
            if(sequence[index]<sequence[end]){
                return false;
            }
            index++;
        }
        //递归判断新的左子树序列和由字数序列
        return verify(sequence,start,mid-1) && verify(sequence,mid,end-1);
    }
}
import java.util.Stack;
import java.util.Arrays;
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence==null || sequence.length==0){
            return false;
        }
        int[] arr = sequence.clone();
        Arrays.sort(arr);
        return IsPopOrder(arr,sequence);
    }
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        int length = pushA.length;
        Stack<Integer> stack = new  Stack<Integer>();
        //i表示圧入栈的位置,j表示弹出序列的位置
        for(int i=0,j=0;i<length;i++){
            stack.push(pushA[i]);
            while(j<length && !stack.isEmpty() && popA[j]==stack.peek()){
                //栈顶出栈
                stack.pop();
                //弹出序列后移一位
                j++;
            }
        }
        return stack.isEmpty();
    }
}
public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence==null || sequence.length==0){
            return false;
        }
        int size = sequence.length;
        int i = 0;
        while((--size)>0){
            while(sequence[i++] < sequence[size]);
            while(sequence[i++] > sequence[size]);
            if(i < size) return false;
            i = 0;
        }
        return true;
    }
}

第24题 二叉树:二叉树中和为某一值的路径

1.题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)。没有考虑到??????
2.考点:树的深度优先遍历。
3.解法:把结点加入路径。比较当前路径和是否等于期待和。若该结点是叶子结点,则加入listAll。否则递归左右子节点。每一轮递归返回到父结点时。

import java.util.ArrayList;
/**没有考虑排序
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
    private ArrayList<Integer> list = new ArrayList<Integer>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root == null){
            return listAll;
        }
        target -= root.val;
        list.add(root.val);
        if(target == 0 && root.left==null && root.right==null){
            listAll.add(new ArrayList<Integer>(list));
            //不重新new的话从始至终listAll中所有引用都指向了同一个list
            //listAll.add(list);[[],[]]
        }else if(target > 0){
            listAll = FindPath(root.left,target);
            listAll = FindPath(root.right,target);
        }
        //递归到叶子节点如果还没有找到路径,就要回退到父节点继续寻找,依次类推
        list.remove(list.size()-1);
        return listAll;
    }
}
import java.util.ArrayList;
/**考虑排序
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    private ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    private ArrayList<Integer> list = new ArrayList<>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        help(root, target);
        Collections.sort(result, new Comparator<ArrayList<Integer>>() {
            @Override
            public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                return o2.size() - o1.size();
            }
        });
        return result;
    }
    
    private void help(TreeNode root, int target) {
        if (root == null)
            return;
        list.add(root.val);
        target -= root.val;
        if (target == 0 && root.left == null && root.right == null)
            result.add(new ArrayList<>(list));
        //根左右/根右左都可以,均属于DFS
        FindPath(root.right, target);
        FindPath(root.left, target);
        list.remove(list.size() - 1);
    }
}

第25题 链表:复杂链表的复制

1.题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
2.考点:链表。
3.解法:
法一:
(1)在每个节点的后面插入复制的节点。(2)对复制的节点的random进行赋值。(3)拆分。时间复杂度O(n)。
法二:
(1)利用空间节省时间(引入hashmap,然后在查找random的时候就可以直接从hashmap中查找对应的节点)。JAVA使用哈希表,时间复杂度O(N),额外空间复杂度O(N)。使用hashmap代替在每个节点后面插入复制的节点。
在这里插入图片描述

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;
    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null){
            return null;
        }
        //在每个节点的后面插入复制的节点
        RandomListNode curNode = pHead;
        RandomListNode insertNode = null;
        while(curNode!=null){
            insertNode = new RandomListNode(curNode.label);
            insertNode.next = curNode.next;
            curNode.next = insertNode;
            curNode = insertNode.next;
        }
        //对复制的节点的random进行赋值
        curNode = pHead;
        while(curNode!=null){
            curNode.next.random = curNode.random == null ? 
                    null:curNode.random.next;
            curNode = curNode.next.next;
        }
        //拆分
        RandomListNode pCloneHead = pHead.next;
        curNode = pHead;
        insertNode = pHead.next;
        while(curNode.next!=null){
            insertNode = curNode.next;
            curNode.next = insertNode.next;
            curNode = insertNode;
        }
        return pCloneHead;
    }
}
//有问题的递归法
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if (pHead == null) return null;
        RandomListNode newNode = new RandomListNode(pHead.label);
        newNode.random = pHead.random;
        newNode.next = Clone(pHead.next);
        return newNode;
    }
}
import java.util.HashMap;
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null) return null;
        HashMap<RandomListNode, RandomListNode> map = new HashMap<>();
        RandomListNode newHead = new RandomListNode(pHead.label);
        RandomListNode pre = pHead;
        RandomListNode newPre = newHead;
        map.put(pre, newPre);
        while(pre.next != null){
            newPre.next = new RandomListNode(pre.next.label);
            pre = pre.next;
            newPre = newPre.next;
            map.put(pre, newPre);
        }
        pre = pHead;
        newPre = newHead;
        while(newPre != null){
            newPre.random = map.get(pre.random);
            pre = pre.next;
            newPre = newPre.next;
        }
        return newHead;
    }
}

第26题 树:二叉搜索树和双向链表

1.题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
2.考点:链表,树。中序遍历二叉排顺序树。
3.解法:
(1)采用二叉树的中序遍历,使用到递归。
(2)高票几个解法要么递归实现,要么借助栈迭代实现,其实都需要O(h)或O(n)的额外空间。这里分享一种非递归、O(1)空间复杂度的解法。该方法通过Morris遍历实现(不熟悉的同学可以搜索Morris Traversal),将二叉树重构为所有结点只有右子树的一条链。
(3)非递归。1.核心是中序遍历的非递归算法。2.修改当前遍历节点与前一遍历节点的指针指向。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    TreeNode head = null;
    TreeNode pre = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        inOrder(pRootOfTree);
        return head;
    }
    //二叉搜索树的中序遍历
    private void inOrder(TreeNode node){
        if(node == null){
            return;
        }
        inOrder(node.left);
        if(head==null){
            head=node;
        }
        node.left = pre;
        if(pre != null){
            pre.right = node;
        }
        pre = node;
        inOrder(node.right);
    }
}
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        TreeNode p = pRootOfTree, pre = null, res = null;
        while (p != null) {
            while (p.left != null) {
                TreeNode q = p.left;
                while (q.right != null) {
                    q = q.right;
                }
                q.right = p;
                TreeNode tmp = p.left;
                p.left = null;
                p = tmp;
            }
            p.left = pre;
            if (pre == null) {
                res = p;
            } else {
                pre.right = p;
            }
            pre = p;
            p = p.right;
        }
        return res;
    }
}
import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null){
            return null;
        }
        TreeNode head =null;
        TreeNode p = pRootOfTree;
        TreeNode pre = null;
        Stack<TreeNode> stack = new Stack<>();
        while(p!=null || !stack.isEmpty()){
            while(p!=null){
                stack.push(p);
                p = p.left;
            }
            p = stack.pop();
            if(head==null){
                head = p;
            }else{
                pre.right = p;
            }
            p.left = pre;
            pre = p;
            p = p.right;
        }
        return head;
    }
}

第27题 字串:字符串的排列

1.题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
2.考点:字符串,动态规划,递归。
3.解法:

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    ArrayList<String> list = new ArrayList<>();
    public ArrayList<String> Permutation(String str) {
        if(str==null || str.length()==0){
            return list;
        }
        char[] chars = str.toCharArray();
        Arrays.sort(chars);//从小到大
        backTracking(chars,new boolean[chars.length],new StringBuilder());
        return list;
    }
    //使用回溯法
    public void backTracking(char[] chars,boolean[] marked,StringBuilder s){
        if(chars.length == s.length()){
            list.add(s.toString());
            return;
        }
        for(int i=0;i<chars.length;i++){
            if(marked[i])
                continue;
            /* 保证不重复 */
            if (i != 0 && chars[i] == chars[i - 1] && !marked[i - 1]) 
                continue;
            marked[i] = true;
            s.append(chars[i]);
            backTracking(chars, marked, s);
            s.deleteCharAt(s.length() - 1);
            marked[i] = false;
        }
    }
}
/**
     * 1、递归算法
     *
     * 解析:http://www.cnblogs.com/cxjchen/p/3932949.html  (感谢该文作者!)
     *
     * 对于无重复值的情况
     *
     * 固定第一个字符,递归取得首位后面的各种字符串组合;
     * 再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合;
     *递归的出口,就是只剩一个字符的时候,递归的循环过程,
      就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
     *
     * 假如有重复值呢?
     * *由于全排列就是从第一个数字起,每个数分别与它后面的数字交换,
     我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这两个数就不交换了。
     * 例如abb,第一个数与后面两个数交换得bab,bba
     。然后abb中第二个数和第三个数相同,就不用交换了。
     * 但是对bab,第二个数和第三个数不 同,则需要交换,得到bba。
     * 由于这里的bba和开始第一个数与第三个数交换的结果相同了
     ,因此这个方法不行。
     *
     * 换种思维,对abb,第一个数a与第二个数b交换得到bab,
     然后考虑第一个数与第三个数交换,此时由于第三个数等于第二个数,
     * 所以第一个数就不再用与第三个数交换了。再考虑bab
     ,它的第二个数与第三个数交换可以解决bba。此时全排列生成完毕!
     *
     *
     * @param str
     * @return
     */
 
public ArrayList<String> Permutation(String str){
 
        ArrayList<String> list = new ArrayList<String>();
        if(str!=null && str.length()>0){
            PermutationHelper(str.toCharArray(),0,list);
            Collections.sort(list);
        }
        return list;
    }
    private void PermutationHelper(char[] chars,int i,ArrayList<String> list){
        if(i == chars.length-1){
            list.add(String.valueOf(chars));
        }else{
            Set<Character> charSet = new HashSet<Character>();
            for(int j=i;j<chars.length;++j){
                if(j==i || !charSet.contains(chars[j])){
                    charSet.add(chars[j]);
                    swap(chars,i,j);
                    PermutationHelper(chars,i+1,list);
                    swap(chars,j,i);
                }
            }
        }
    }
 
    private void swap(char[] cs,int i,int j){
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }


 
/**
     * 2、字典序排列算法
     *
     * 可参考解析: 
     http://www.cnblogs.com/pmars/archive/2013/12/04/3458289.html  
     (感谢作者)
     *
     * 一个全排列可看做一个字符串,字符串可有前缀、后缀。
     * 生成给定全排列的下一个排列.所谓一个的下一个就是这一个
     与下一个之间没有其他的。
     * 这就要求这一个与下一个有尽可能长的共同前缀,
     也即变化限制在尽可能短的后缀上。
     *
     * [例]839647521是1--9的排列。1—9的排列最前面的是123456789,
     最后面的987654321,
     * 从右向左扫描若都是增的,就到了987654321,也就没有下一个了。
     否则找出第一次出现下降的位置。
     *
     * 【例】 如何得到346987521的下一个
     * 1,从尾部往前找第一个P(i-1) < P(i)的位置
     * 3 4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
     * 最终找到6是第一个变小的数字,记录下6的位置i-1
     *
     * 2,从i位置往后找到最后一个大于6的数
     * 3 4 6 -> 9 -> 8 -> 7 5 2 1
     * 最终找到7的位置,记录位置为m
     *
     * 3,交换位置i-1和m的值
     * 3 4 7 9 8 6 5 2 1
     * 4,倒序i位置后的所有数据
     * 3 4 7 1 2 5 6 8 9
     * 则347125689为346987521的下一个排列
     *
     * @param str
     * @return
     */
 
public ArrayList<String> Permutation2(String str){
        ArrayList<String> list = new ArrayList<String>();
        if(str==null || str.length()==0){
            return list;
        }
        char[] chars = str.toCharArray();
        Arrays.sort(chars);
        list.add(String.valueOf(chars));
        int len = chars.length;
        while(true){
            int lIndex = len-1;
            int rIndex;
            while(lIndex>=1 && chars[lIndex-1]>=chars[lIndex]){
                lIndex--;
            }
            if(lIndex == 0)
                break;
            rIndex = lIndex;
            while(rIndex<len && chars[rIndex]>chars[lIndex-1]){
                rIndex++;
            }
            swap(chars,lIndex-1,rIndex-1);
            reverse(chars,lIndex);
 
            list.add(String.valueOf(chars));
        }
 
        return list;
    }
 
    private void reverse(char[] chars,int k){
        if(chars==null || chars.length<=k)
            return;
        int len = chars.length;
        for(int i=0;i<(len-k)/2;i++){
            int m = k+i;
            int n = len-1-i;
            if(m<=n){
                swap(chars,m,n);
            }
        }
 
    }
 }
import java.util.*;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> re = new ArrayList<String>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet<String> set = new HashSet<String>();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet<String> re, char[] str, int k) {
        if (k == str.length) {
            re.add(new String(str));
            return;
        }
        for (int i = k; i < str.length; i++) {
            swap(str, i, k);
            fun(re, str, k + 1);
            swap(str, i, k);
        }
    }
    void swap(char[] str, int i, int j) {
        if (i != j) {
            char t = str[i];
            str[i] = str[j];
            str[j] = t;
        }
    }
}
public ArrayList<String> Permutation(String str) {
       ArrayList<String> res = new ArrayList<>();
 
        if (str != null && str.length() > 0) {
            char[] seq = str.toCharArray();
            Arrays.sort(seq); //排列
            res.add(String.valueOf(seq)); //先输出一个解
 
            int len = seq.length;
            while (true) {
                int p = len - 1, q;
                //从后向前找一个seq[p - 1] < seq[p]
                while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                if (p == 0) break; //已经是“最小”的排列,退出
                //从p向后找最后一个比seq[p]大的数
                q = p; --p;
                while (q < len && seq[q] > seq[p]) q++;
                --q;
                //交换这两个位置上的值
                swap(seq, q, p);
                //将p之后的序列倒序排列
                reverse(seq, p + 1);
                res.add(String.valueOf(seq));
            }
        }
 
        return res;
    }
     
    public static void reverse(char[] seq, int start) {
        int len;
        if(seq == null || (len = seq.length) <= start)
            return;
        for (int i = 0; i < ((len - start) >> 1); i++) {
            int p = start + i, q = len - 1 - i;
            if (p != q)
                swap(seq, p, q);
        }
    }
     
    public static void swap(char[] cs, int i, int j) {
        char temp = cs[i];
        cs[i] = cs[j];
        cs[j] = temp;
    }
}
import java.util.*;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> re = new ArrayList<String>();
        if (str == null || str.length() == 0) {
            return re;
        }
        HashSet<String> set = new HashSet<String>();
        fun(set, str.toCharArray(), 0);
        re.addAll(set);
        Collections.sort(re);
        return re;
    }
    void fun(HashSet<String> re, char[] str, int k) {
        if (k == str.length) {
            re.add(new String(str));
            return;
        }
        for (int i = k; i < str.length; i++) {
            swap(str, i, k);
            fun(re, str, k + 1);
            swap(str, i, k);
        }
    }
    void swap(char[] str, int i, int j) {
        if (i != j) {
            char t = str[i];
            str[i] = str[j];
            str[j] = t;
        }
    }
}

第28题 数组:数组中出现次数超过一半的数字

1.题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
2.考点:数组
3.解法:(1)使用排序数组的规律。(2)使用HashMap。(3)使用多数表决。
//首先对数组进行排序;如果存在超过数组长度一半的数,则该值应该是数组中间位置的值;
//遍历数组,查看中间位置的值在数组中的出现次数是否大于一半。
//这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length==0 || array==null){
            return 0;
        }
        Arrays.sort(array);
        int mid = array[array.length/2];
        int count = 0;
        for(int i : array){
            if(i == mid){
                count++;
            }
        }
        return count > array.length/2 ? mid : 0;
    }
}
import java.util.HashMap;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array==null || array.length==0){
            return 0;
        }
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<array.length;i++){
            if(map.containsKey(array[i])){
                map.put(array[i],map.get(array[i])+1);
            }else{
                map.put(array[i],1);
            }
        }
        for(Integer t : map.keySet()){
            if(map.get(t)>(array.length/2)){
                return t;
            }
        }
        return 0;
    }
}
/*多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。*/
	/*使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt++,否则令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。*/
import java.util.Arrays;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array.length==0 || array==null){
            return 0;
        }
        int count=1;//标记是否有majority
        int majority = array[0];//存放majority
        for(int i=1;i<array.length;i++){
            if(majority == array[i]){
                count++;
            }else{
                count--;
            }
            if(count == 0){//说明前面i个数没有majority
                count = 1;
                majority = array[i];
            }
        }
        count = 0;
        for(int i : array){
            if(i == majority){
                count++;
            }
        }
        return count > array.length/2 ? majority : 0;
    }
}

第29题 数组:最小的k个数

1.字母:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
2.考点:数组。高级算法。/**基于堆排序算法,构建最大堆。时间复杂度为O(nlogk)。如果用快速排序,时间复杂度为O(nlogn)。如果用冒泡排序,时间复杂度为O(nk)。/
3.解法:(1)排序再抽取k个。(2)大小为k的最小堆, 复杂度O(NlogK)+O(K),使用最大堆来保存着看个数,在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。(3)快速排序,复杂度O(N)+O(1),只有当允许修改数组元素时才可以使用。快速排序的 partition() 方法,会返回一个整数 j 使得 a[l…j-1] 小于等于 a[j],且 a[j+1…h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。
4.用PriorityQueue实现最大最小堆
(1)PriorityQueue(优先队列),一个基于优先级堆的无界优先级队列。实际上是一个堆(不指定Comparator时默认为最小堆),通过传入自定义的Comparator函数可以实现大顶堆。
(2)PriorityQueue的常用方法有:

  • poll(),offer(Object),size(),peek()等。
  • 插入方法(offer()、poll()、remove() 、add() 方法)时间复杂度为O(log(n)) ; remove(Object) 和 contains(Object) 时间复杂度为O(n);
  • 检索方法(peek、element 和 size)时间复杂度为常量。(堆)
    PriorityQueue minHeap = new PriorityQueue();
    //小顶堆,默认容量为11
 PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(11,new Comparator<Integer>(){ //大顶堆,容量11
  @Override
   public int compare(Integer i1,Integer i2){
		return i2-i1;
   }
});
import java.util.Arrays;
import java.util.ArrayList;
public class Solution {
    private ArrayList<Integer> list = new ArrayList<Integer>();
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        int length = input.length;
        if(k > length || k == 0){
            return list;
        }
        Arrays.sort(input);
        for(int i=0;i<k;i++){
            list.add(input[i]);
        }
        return list;
    }
}
import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
    private ArrayList<Integer> list = new ArrayList<Integer>();
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        int length = input.length;
        if(input == null || k > length || k <= 0){
            return list;
        }
        //创建一个大顶堆
        PriorityQueue<Integer> maxHeap = 
                    new PriorityQueue<Integer>(new Comparator<Integer>(){
            public int compare(Integer i1,Integer i2){
                return i2-i1;
            }
        } );
 //大顶堆圧入数据,前k个最小值,第k+1个时开始判断,其中堆顶存放的是k个数据最大值
        for(int nums : input){
            maxHeap.add(nums);
            if(maxHeap.size() > k){
                maxHeap.poll();
            }
        }
        //将大顶堆的数据复制到list
        for (Integer integer : maxHeap) {
            list.add(integer);
        }
        return list;//return new ArrayList(maxheap);
    }
}
(冒泡排序,只不过最外层是k次)
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> al = new ArrayList<Integer>();
        if (k > input.length) {
            return al;
        }
        for (int i = 0; i < k; i++) {
            for (int j = 0; j < input.length - i - 1; j++) {
                if (input[j] < input[j + 1]) {
                    int temp = input[j];
                    input[j] = input[j + 1];
                    input[j + 1] = temp;
                }
            }
            al.add(input[input.length - i - 1]);
        }
        return al;
    }
}
import java.util.Arrays;
import java.util.ArrayList;
public class Solution {
    private ArrayList<Integer> list = new ArrayList<Integer>();
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        int length = input.length;
        if(input == null || k > length || k <= 0){
            return list;
        }
        //findKthSmallest(int[] input,int k-1) 会更改数组位置
        //在数组中,小于k-1位置的元素都小于k-1的值,大于k-1位置的元素都大于k-1的值
        //位置k-1的值是该数组中第k小值
        findKthSmallest(input,k-1);
        for(int i=0 ; i<k ; i++){
            list.add(input[i]);
        }
        return list;
    }

    private void findKthSmallest(int[] input, int k){
        int low = 0;
        int high = input.length-1;
        int index;
        while(low<high){
            index = partition(input , low , high);
            if(index == k){
                break;
            }else if(index > k){
                high = index - 1;
            }else{
                low = index + 1;
            }
        }
    }
    
    private int partition(int[] input ,int low ,int high){
        //切分元素,比input[low]大的都位于右侧,比input[low]小的都位于左侧
        //返回input[low]所在的位置
        int value = input[low];
        int i=low;
        int j=high+1;
        while(true){
            while(i != high && input[++i]<value);
            while(j!= low && input[--j]>value);
            if(j<i){
                swap(input ,low,j);
                break;
            }
            swap(input,i,j);
        }
        return j;
    }
    
    private void swap(int[] input ,int i, int j){
        int temp = input[i];
        input[i] = input[j];
        input[j] = temp;
    }
}

第30题 数组:连续子数组的最大和

1.题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
2.考点:数组。初级动态规划。
3.解法:

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array == null || array.length == 0){
            return 0;
        }
        int result = array[0];//存放所有子数组的最大值
        int max = array[0];//存放连续子数组的最大值
        for(int i=1;i<array.length;i++){
            max = (max+array[i])> array[i] ? (max+array[i]):array[i];
            result = result>max ? result : max;
        }
        return result;
    }
} 

第31题 整数:整数中1出现的次数

在这里插入图片描述

 public class Solution {//23mm
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n<=0){
            return 0;
        }
        int count = 0;
        int val = 0;
        for(int i=1;i<=n;i++){
            //循环判断数字的个十百千等位数有没有1
            val = i;
            while(val!= 0){
                //val%10==n 判断数字的个十百千等位有没有1
                if(val%10 == 1){
                    count++;
                }
                val = val/10;
            }
        }
        return count;
    }
}
public class Solution {//86mm
    public int NumberOf1Between1AndN_Solution(int n) {
        int sum = 0;
        for(int i=1;i<=n;i++){
            String s = i+"";
            for(int j=0;j<s.length();j++){
                if(s.charAt(j) == '1'){
                    sum++;
                }
            }
        }
        return sum; 
    }
}
public class Solution {//14mm
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n<=0){
            return 0;
        }
        int i;
        int a,b;
        int count = 0;
        for(i=1;i<=n;i*=10){
            a=n/i;
            b=n%i;
            count += (a+8)/10*i + (a%10 == 1 ? b+1 : 0);
        }
        return count;
    }
}

第32题 数组:把数组排成最小的数

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

  • 先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
  • 排序规则如下:
  • 若ab > ba 则 a > b,
  • 若ab < ba 则 a < b,
  • 若ab = ba 则 a = b;
  • 解释说明:
  • 比如 “3” < "31"但是 “331” > “313”,所以要将二者拼接起来进行比较。
    4.Java的Arrays类中有一个sort()方法,该方法是Arrays类的静态方法,在需要对数组进行排序时,是非常好用的,一般需要排序的时候,我是直接使用;如果是逆序排序需要自己定义排序规则。时间复杂度:O(n.logn)。空间复杂度:O(1)。
    5.Java compareTo() 方法:
    (1)compareTo() 方法用于两种方式的比较:字符串与对象进行比较和按字典顺序比较两个字符串。
    (2)返回值:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers==null || numbers.length==0){
            return "";
        }
        String[] str = new String[numbers.length];
        String result = "";
        for(int i=0;i<numbers.length;i++){
            str[i]=""+numbers[i];
        }
        Arrays.sort(str,new Comparator<String>(){
            public int compare(String s1,String s2){
                return (s1+s2).compareTo(s2+s1);
            }
        });//21mm
        /*//使用lambda表达式简化代码:
        Arrays.sort(str ,(s1,s2) -> (s1+s2).compareTo(s2+s1));
        *///234mm
        for(String temp : str){
            result += temp;
        }
        return result;
    }
}
/*所以在这里自定义一个比较大小的函数,比较两个字符串s1,s2大小的时候,
先将其堆叠起来,比较s1 + s2,和s2 + s1那个大,如果s1 + s2大,
那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。
*/
public class Solution {//19mm
    public String PrintMinNumber(int [] numbers) {
        if(numbers==null || numbers.length==0){
            return "";
        }
        String[] str = new String[numbers.length];
        for(int i=0;i<numbers.length;i++){
            str[i]=""+numbers[i];
        }
        for(int i=0;i<numbers.length;i++){
            for(int j=i+1;j<numbers.length;j++){
                int a = Integer.valueOf(str[i]+str[j]);
                int b = Integer.valueOf(str[j]+str[i]);
                if(a>b){
                    String temp = str[i];
                    str[i] = str[j];
                    str[j] = temp;
                }
            }
        }
        String res = "";
        for(String s : str){
            res += s;
        }
        return res;
    }
}

第33题 数字:丑数

1.题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
2.考点:丑数,动态规划。
3.解法:
(1)解法一:按照顺序判断每个数是不是丑数。缺点:即使一个数不是丑数, 还是需要对它进行计算。根据丑数的定义,丑数只能被2、3和5整数。也就是说如果一个数能被2整除,我们就把它连续除以2;如果能被3整数,就连续除以3;如果能被5整除,就除以连续5。如果最后我们得到的数字是1,那么这个数就是丑数,否则不是。
(2)解法二:创建数组保存已找到的丑数,用空间换时间的解法.

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=0){
            return 0;
        }
        int count=1;
        int i=1;
        while(count<index){
            i++;
            if(isUglyNumber(i)){
                count++;
            }
        }
        return i;
    }
    // 判断一个数是不是丑数
    boolean isUglyNumber(int number) {
        while(number % 2 == 0)
            number /= 2;
        while(number % 3 == 0)
            number /= 3;
        while(number % 5 == 0)
            number /= 5;
        return (number == 1) ? true : false;
    }
}
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=6){
            return index;
        }
        int[] arr = new int[index];
        arr[0]=1;
        int next2=2;
        int next3=0;
        int next5=0;
        int min;
        int i2=0,i3=0,i5=0;
        for(int i=1;i<index;i++){
            next2 = arr[i2]*2; 
            next3 = arr[i3]*3;
            next5 = arr[i5]*5;
            min = Math.min(next2,Math.min(next3,next5));
            if(min==next2){
                i2++;
            }
            if(min==next3){
                i3++;
            }
            if(min==next5){
                i5++;
            }
            arr[i]=min;
        }
        
        return arr[index-1];
    }
}

第34题 字符串:第一个只出现一次的字符

1.题目:在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
2.考点:字符串。
3.解法:
(1)从头开始扫描这个字符串中的每个字符。当访问到某字符时,拿这个字符与后面的每个字符比较,如果在后面没有发现重复的字符,则该字符就是只出现一次的字符。时间复杂度O(N2)。
(2)构建键值对,键值是字符,值是字符出现的次数。

import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str==null || str.length()==0){
            return -1;
        }
        int[] arr = new int[256];
        char[] chars = str.toCharArray();
        Queue<Integer> queue = new LinkedList<>();
        for(int i=0;i<chars.length;i++){
            arr[chars[i]] = arr[chars[i]] +1;
            queue.add(i);
            while(!queue.isEmpty() && arr[chars[queue.peek()]]>1){
                queue.poll();
            }
        }
        return queue.isEmpty() ? -1 : (queue.peek());
    }
}
public static int solution(String str){
    int[] words = new int[58];
    for(int i = 0;i<str.length();i++){
        words[((int)str.charAt(i))-65] += 1;
    }
    for(int i=0;i<str.length();i++){
        if(words[((int)str.charAt(i))-65]==1)
            return i;
    }
    return -1;
}
public int FirstNotRepeatingChar(String str) {
    int[] cnts = new int[256];
    for (int i = 0; i < str.length(); i++)
        cnts[str.charAt(i)]++;
    for (int i = 0; i < str.length(); i++)
        if (cnts[str.charAt(i)] == 1)
            return i;
    return -1;
}
public int FirstNotRepeatingChar2(String str) {
    BitSet bs1 = new BitSet(256);
    BitSet bs2 = new BitSet(256);
    for (char c : str.toCharArray()) {
        if (!bs1.get(c) && !bs2.get(c))
            bs1.set(c);     // 0 0 -> 0 1
        else if (bs1.get(c) && !bs2.get(c))
            bs2.set(c);     // 0 1 -> 1 1
    }
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (bs1.get(c) && !bs2.get(c))  // 0 1
            return i;
    }
    return -1;
}
import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str==null){
            return -1;
        }
        HashMap<Character,Integer> map = new HashMap<Character,Integer>();
        int time = 0;
        for(int i=0;i<str.length();i++){
            if(map.containsKey(str.charAt(i))){
                time = map.get(str.charAt(i));
                map.put(str.charAt(i),++time);
            }else{
                map.put(str.charAt(i),1);
            }
        }
        for(int i=0 ;i<str.length();i++){
            if(map.get(str.charAt(i))==1){
                return i;
            }
        }
        return -1;
    }
}

第35题 数组:数组中的逆序对

1.题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。题目保证输入的数组中没有的相同的数字。数据范围:对于%50的数据,size<=104;对于%75的数据,size<=105;对于%100的数据,size<=2*10^5。
2.考点:数组,归并排序(nlog(n))。
3.解法:
(1)遍历整个数组,每次遍历一个数值时,逐个比较该数字与它后面的数字的大小。时间复杂度O(N2)。
(2)我们以数组{7,5,6,4}为例来分析统计逆序对的过程。每次扫描到一个数字的时候,我们不拿ta和后面的每一个数字作比较,否则时间复杂度就是O(n^2),因此我们可以考虑先比较两个相邻的数字。
在这里插入图片描述
(a) 把长度为4的数组分解成两个长度为2的子数组;
(b) 把长度为2的数组分解成两个成都为1的子数组;
© 把长度为1的子数组 合并、排序并统计逆序对 ;
(d) 把长度为2的子数组合并、排序,并统计逆序对;
在上图(a)和(b)中,我们先把数组分解成两个长度为2的子数组,再把这两个子数组分别拆成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7大于5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6}、{4}中也有逆序对(6,4)。由于我们已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组 排序 如上图(c)所示, 以免在以后的统计过程中再重复统计。
接下来我们统计两个长度为2的子数组子数组之间的逆序对。合并子数组并统计逆序对的过程如下图 如下图所示。
我们先用两个指针分别指向两个子数组的末尾,并每次比较两个指针指向的数字。如果第一个子数组中的数字大于第二个数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数,如下图(a)和(c)所示。如果第一个数组的数字小于或等于第二个数组中的数字,则不构成逆序对,如图b所示。每一次比较的时候,我们都把较大的数字从后面往前复制到一个辅助数组中,确保 辅助数组(记为copy) 中的数字是递增排序的。在把较大的数字复制到辅助数组之后,把对应的指针向前移动一位,接下来进行下一轮比较。
在这里插入图片描述

过程:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。参考代码如下:

public class Solution {
    static long count=0;
    public int InversePairs(int [] array) {
        if(array != null){
             int[] temp = new int[array.length];
             mergeSort(array,0,array.length-1,temp);
        }
        return (int)count;
    }
    //分加和方法
	public static void mergeSort(int[] arr,int left,int right,int[] temp) {
		if(left<right) {
			int mid = (left + right)/2;
			//向左递归进行分解
			mergeSort(arr, left, mid, temp);
			//向右递归进行分解
			mergeSort(arr, mid +1, right,temp);
			//合并
			merge(arr, left, mid, right, temp);
		}
	}

	//合并方法
	public static void merge(int[] arr,int left,int mid,
                                     int right,int[] temp) {
		int i = left;//初始化i,左边有序序列的初始索引
		int j = mid +1;//初始化将,右边有序序列的初始索引
		int t = left;//指向temp数组的当前索引
		
		//先把左右两边的(有序)数据按照规则填充到temp数组
		//知道左右两边的有序序列有一边处理完毕为止
		while(i<=mid && j<=right) {
			if(arr[i]<=arr[j]) {
				temp[t] = arr[i];
				t++;
				i++;
			}else {
				temp[t] = arr[j];
				t++;	
				j++;
                //说明mid-i+1个都与j构成了逆序对
                //nums[i] > nums[j],说明 nums[i...mid] 都大于 nums[j]
                count=(count+(mid-i+1))%1000000007;//防止溢出
			}
		}
		//把剩余数据的一边的数据以次填充到temp
		while(i<=mid) {
			temp[t] = arr[i];
			i++;
			t++;
		}
		while(j<=right) {
			temp[t] =arr[j];
			j++;
			t++;
		}
		//System.out.println("left:"+left+"--"+"right"+right);
		//将temp数组拷贝到arr
		t=left;
		while(t<=right) {//注意并不是每次都拷贝所有 temp
			arr[t] = temp[t];
			t++;
		}
	}
}
private long cnt = 0;
private int[] tmp;  // 在这里声明辅助数组,而不是在 merge() 递归函数中声明

public int InversePairs(int[] nums) {
    tmp = new int[nums.length];
    mergeSort(nums, 0, nums.length - 1);
    return (int) (cnt % 1000000007);
}

private void mergeSort(int[] nums, int l, int h) {
    if (h - l < 1)
        return;
    int m = l + (h - l) / 2;
    mergeSort(nums, l, m);
    mergeSort(nums, m + 1, h);
    merge(nums, l, m, h);
}

private void merge(int[] nums, int l, int m, int h) {
    int i = l, j = m + 1, k = l;
    while (i <= m || j <= h) {
        if (i > m)
            tmp[k] = nums[j++];
        else if (j > h)
            tmp[k] = nums[i++];
        else if (nums[i] <= nums[j])
            tmp[k] = nums[i++];
        else {
            tmp[k] = nums[j++];
            this.cnt += m - i + 1;  
            // nums[i] > nums[j],说明 nums[i...mid] 都大于 nums[j]
        }
        k++;
    }
    for (k = l; k <= h; k++)
        nums[k] = tmp[k];
}

第36题 链表:两个链表的第一个公共结点

1.题目:输入两个链表,找出它们的第一个公共结点。
2.考点:链表。
3.解法:
(1)
假设A的长度为a,B的长度为b,公共部分C的长度为c。链表1的长度为a+c,链表2的长度为b+c。则有a+c+b = b+c+a。a+c+b-c = b+c+a-c。
当访问链表A的指针访问到链表尾部时,令它从链表B的头部重新开始访问链表B;同样地,当访问链表B的指针访问到链表尾部时,令其从链表A的指向重新这样可以控制访问A和B两个链表的指针能同时访问到交点。
用两个指针扫描”两个链表“,最终两个指针到达 null 或者到达公共结点。
在这里插入图片描述

(2)
利用HashMap的特性。设表1长度n,表2长度m,暴力法嵌套遍历两个链表需要O(mn)的时间复杂度, 可以采用hash的思想将其中一个转存为哈希表结构,这样建哈希表时间O(m), 而遍历链表时间O(n),而遍历时查找哈希表的时间为O(1),因此复杂度降为O(m+n),但需要辅助空间。(这种哈希优化的策略是种一般性的思路,谨记!)
(3)
两条相交的链表呈Y型。可以从两条链表尾部同时出发,最后一个相同的结点就是链表的第一个相同的结点。可以利用栈来实现。时间复杂度有O(m + n), 空间复杂度为O(m + n)

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1 = (p1==null) ? pHead2:p1.next;
            p2 = (p2==null) ? pHead1:p2.next;
        }
        return p1;
    }
}
import java.util.HashMap;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        HashMap<ListNode,Integer> map = new HashMap<ListNode,Integer>();
        while(pHead1 != null){
            map.put(pHead1,null);
            pHead1 = pHead1.next;
        }
        while(pHead2 != null){
            if(map.containsKey(pHead2)){
                return pHead2;
            }
            pHead2 = pHead2.next;
        }
        return null;
    }
}
import java.util.HashMap;
import java.util.Stack;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1==null || pHead2==null){
            return null;
        }
        Stack<ListNode> stack1 = new Stack<ListNode>();
        Stack<ListNode> stack2 = new Stack<ListNode>();
        while(pHead1 != null){
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        }
        while(pHead2 != null){
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }
        ListNode restNode = null;
        while(!stack1.isEmpty() && !stack2.isEmpty() 
                    && stack1.peek()==stack2.peek()){
            restNode = stack1.peek();
            stack1.pop();
            stack2.pop();
        }
        return restNode;
    }
}

第37题 数组:数字排序数组中出现的次数

1.题目:统计一个数字在排序数组中出现的次数。
2.考点:有序数组,二分查找。
3.解法:
(1)使用二分查找找到k所在的某一个位置,再从该位置向左向右判断是否是k,时间复杂度(o(log(n)+count))。
(2)由于是整数数组,可以利用二分查找找到k所在的位置first1,k+1所在的位置first2,则fast-first就是k在数组中出现的次数,时间复杂度(o(log(n))。
(3)暴力遍历整个数组判断是不是k,时间复杂度(o(n))

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        if(array.length == 0){
            return 0;
        }
        //开始统计出现的次数
        int index = binarySearch1(array,k);
        if(index == -1){
            return 0;
        }
        int count = 1;
        int i=index-1;
        int j=index+1;
        while(i>=0 && array[i]==k){
            count++;
            i--;
        }
        while(j<array.length && array[j]==k){
            count++;
            j++;
        }
        return count;
    }
    //二分查找算法
    private int binarySearch1(int[] array,int k){
        int low = 0;
        int high = array.length-1;
        int mid;
        while(low<=high){
            mid = (high+low)>>1;
            if(array[mid]<k){
                low = mid+1;
            }else if(array[mid]>k){
                high = mid-1;
            }else{
                return mid;
            }
        }
        return -1;
    }
}
public class Solution {
        public  int GetNumberOfK(int[] array,int k){
        if(array==null||array.length==0)
            return 0;
        int first=getFirstK(array,k,0,array.length-1);
        int last=getLastK(array,k,0,array.length-1);
        if(first==-1 ||last==-1){
            return 0;
        }
        else{
            return last-first+1;
        }
    }
     
    public  int getFirstK(int[] array,int k,int start,int end){
        while(start<=end){
            int mid=(start+end)/2;
            if(k<array[mid])
                end=mid-1;
            else if(k>array[mid])
                start=mid+1;
            else{
                if((mid>0&&array[mid-1]!=k)||mid==0)
                    return mid;
                else{
                    end=mid-1;
                }
            }
        }
        return -1;
    }
     
    public  int getLastK(int[] array,int k ,int start,int end){
        while(start<=end){
            int mid=(start+end)/2;
            if(k<array[mid])
                end=mid-1;
            else if(k>array[mid])
                start=mid+1;
            else{
                if((mid<array.length-1&&array[mid+1]!=k)||mid==array.length-1)
                    return mid;
                else{
                    start=mid+1;
                }
            }
        }
        return -1;
    }
}
public class Solution {
    public int GetNumberOfK(int[] nums, int K) {
        int first = binarySearch(nums, K);
        int last = binarySearch(nums, K + 1);
        return (first == nums.length || nums[first] != K) ? 0 : last - first;
    }
    
    private int binarySearch(int[] nums, int K) {
        int l = 0, h = nums.length;
        while (l < h) {
            int m = l + (h - l) / 2;
            if (nums[m] >= K)
                h = m - 1;
            else
                l = m + 1;
        }
        return l;
    }
}

第38题 二叉树:二叉树的深度

1.题目:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
2.考点:递归。
3.解法:
(1)递归的方法:如果当前节点不为空,以次寻找当前节点左右子树的深度的最大值。递归求解:
假如是空节点,则返回0;否则,原树的深度由左右子树中深度较的深度加1,为原树的深度
(2)非递归的方法:使用层次遍历。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    int deep=0;
    public int TreeDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        return 1+Math.max(TreeDepth(root.left),TreeDepth(root.right));
    }
}
import java.util.Queue;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    int deep=0;
    public int TreeDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        //使用层次遍历
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        int depth=0;//记录深度,层次数
        int count=0;//记录每一层的第几个节点
        int counts=1;//记录每一层的节点总数
        TreeNode node = null;//记录每次从队列中poll出的节点
        while(queue.size()!=0){
            node = queue.poll();
            count++;
            if(node.left != null){
                queue.add(node.left);
            }
            if(node.right != null){
                queue.add(node.right);
            }
            if(count == counts){
                counts = queue.size();
                count = 0;
                depth++;
            }
        }
        return depth;
    }
}

第39题 二叉树:平衡二叉树

1.题目:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
2.考点:平衡二叉树(AVL树)的概念(它或者是一颗空树,或者具有以下性质的二叉排序树:它的左子树和右子树的深度之差(平衡因子)的绝对值不超过1,且它的左子树和右子树都是一颗平衡二叉树)。
3.解法:
(1)递归:比较每个节点左右子树的深度,如果子树不是平衡二叉树,则直接返回。

public class Solution {
    boolean isBalanced = true;//用于标记是否是平衡二叉树
    public boolean IsBalanced_Solution(TreeNode root) {
        int depth = height(root);//树的深度
        return isBalanced;
    }
    public int height(TreeNode node){
        if(node==null || !isBalanced){
            return 0;
        }
        int left = height(node.left);
        int right = height(node.right);
        if(Math.abs(left-right)>1){
            isBalanced = false;
        }
        return 1+Math.max(left,right);
    }
}
public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return getDepth(root) != -1;
    }
     
    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        int left = getDepth(root.left);
        if (left == -1) return -1;
        int right = getDepth(root.right);
        if (right == -1) return -1;
        return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
    }
}

第40题 数组:数组中只出现一次的数字

1.题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
2.考点:哈希表。
3.解法:
(1)哈希表:考虑数组中有重复的数字和不重复的数字,则利用哈希表的特性。
(2)异或:1、异或思想,一个数与自己异或为0,一个数与0异或为自己。2、由于其它数字两两相同,所以所有数异或则得到这两个不同数的异或结果。取这个结果的第一个1作为标志位。3、这个标志的1,必须是:这两个数在该位一个为0,一个为1。4、这样可以将数组分为两组,一组在该标志位为1,一组在该标志位为0,这两个不同数字分别在这两组内。5、将两组内的数分别异或,得到两个结果则为这两个不同的数
(3)使用堆栈来做辅助功能,将数组先排序,依次入栈,每一次数组入栈时和当前堆栈的栈头比较,如果当前堆栈为空,就入栈,如果和当前栈头的元素相同就出栈,当数组中左右元素都入栈完毕,那么当前栈中剩余的2个元素就是只出现一次的两个元素。下标index从1到n-2,比较data[index-1]、data[index]和data[index+1]是否有相等的情况就行了,只需要比较相邻的两个元素,data[index-1]和data[index+1]没有比较的必要,因为它们要是相等的话那三个数肯定都相等,用data[index-1]、data[index]就能比较出结果来了,data[index-1]和data[index+1]不相等的话得不到任何信息。

import java.util.HashMap;
import java.util.Iterator;
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if(array==null){
            return;
        }
        //考虑数组中有重复的数字和不重复的数字,则利用哈希表的特性。
        //1.将数组中的元素放入到哈希表中,键值是数组值,value是数组值出现的次数。
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])){
                map.put(array[i],1);
            }else{
                map.put(array[i],map.get(array[i])+1);
            }
        }
        //2.遍历哈希表,寻找value值是1的键值,并将其存入到num1数组,num2数组中。
        int count = 1;
        Integer key = null;
        Integer integ = null;
        Iterator iter = map.keySet().iterator();
        while (iter.hasNext()) {
            // 获取key
            key = (Integer)iter.next();
            // 根据key,获取value
            integ = (Integer)map.get(key);
            if(integ==1 && count==1){
                num1[0] = key;
                count++;
            }
            if(integ==1 && count==2){
                num2[0] = key;
            }
        }
    }
}
import java.util.HashSet;
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        if(array==null){
            return;
        }
        //考虑数组中有重复的数字和不重复的数字,则利用哈希表的特性。
        //1.将数组中的元素放入到哈希表中,键值是数组值,value是数组值出现的次数。
        HashSet<Integer> set = new HashSet<Integer>();
        for(int i=0;i<array.length;i++){
            if(!set.contains(array[i])){
                set.add(array[i]);
            }else{
                set.remove(array[i]);
            }
        }
        //2.遍历哈希表,并将其存入到num1数组,num2数组中。
        if(set.size()==2){
            int count =1;
            for(Integer i:set){
                if(count==1){
                    num1[0]=i;
                    count++;
                }else{
                    num2[0]=i;
                }
            }
        }
    }
}
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
//【解决思路】:从简单的场景想起,假设一个数组中只有一个独特元素,其他出现次数都为2
//如何快速找出这个独特元素呢?那就是从头到尾两两异或,由于相同的数异或为0,则认为是抵消
//一直到最后,结果必然就是这个独特元素
//那么找出两个来也是这个思路,核心就是要将这两个独特的数分离开,下面详细介绍  
        if(array == null || array.length <= 1){
            num1[0] = num2[0] = 0;
            return;
        }

        //整个数组从头两两异或,最终的结果必然是两个不同数字的异或结果
        //因为相同的数字两两异或之后为0
        //0和任意一个数异或还是这个数本身
        int len = array.length, index = 0, sum = 0;
        for(int i = 0; i < len; i++){
            sum ^= array[i];
        }
 
        //java中int类型占4个字节,即32个bit
        //从右开始找到这个异或结果第一个为1的索引
        while((sum&1) == 0 && index < 32){
            sum = sum >> 1;
            index++;
        }
 
        //以这个索引处是否为1作为判定标准,就将两个不同的数分离开了
        //下面就是分两批不停地疑惑,就会得到这两个不同的数
        for(int i = 0; i < len; i++){
            //这样就可以分别找到index处为1的独特解以及为0的独特解
            if(((array[i]>>index)&1)==1){
                num1[0] ^= array[i];
            }else{
                num2[0] ^= array[i];
            }
        }
    }
}
/*diff&= -diff得到出diff最右边不为0的位,
也就是不存在重复的两个元素在位级表示上最右边不同的那一位,利用这一位就可以将两个元素区分开来。
*/
public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
    int diff = 0;
    for (int num : nums)
        diff ^= num;
    diff &= -diff;
    for (int num : nums) {
        if ((num & diff) == 0)
            num1[0] ^= num;
        else
            num2[0] ^= num;
    }
}

第41题 序列:和为S的连续整数序列

1.题目:
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
2.考点:穷举。
3.解法:
(1)双指针法:双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,我们根据窗口内值之和来确定窗口的位置和宽度。
(2)利用数学知识把其分为奇偶。
n = 2k + 1时,n项连续正数序列的和为S的条件: n & 1 && S / n == 0 解读 逻辑与的左边要求n为奇数,右边要求整个序列的平均数恰好为中间数。
n = 2k时,n项连续正数序列的和为S的条件: S % n * 2 == n 解读 S % n 的结果是中间两项左边的那项,乘2刚好是项数。举例,现有S = 39,6个连续正数序列和式能不能为S呢?套用公式,39 % 6 * 2 =6 == 6,我们也知道,这次的序列是 4、5、6、7、8、9,取余的结果为3对应着值为6的那一项,也就是中间项左边的那一项。
和为S,项数为n,如何写出这个序列? S / n - (n-1) / 2 解读 执行的除法是地板除法(floor),不管最终结果有无小数都直接舍去。仍使用上述例子,39 / 6 = 6,6恰好是中间项左边的那一项,6 - (6-1)/ 2 = 4,恰好是序列最左端。序列写出来就没问题。
(3)

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        //存放结果
        ArrayList<ArrayList<Integer>> restList = new ArrayList<>();
        if(sum <= 2){
            return restList;
        }
        
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int left = 1;
        int right = 2;
        while(left<right){
            //left到right的和,等差数列,(a0+a1)*n/2。
            int total = (left+right)*(right-left+1)/2;
            //相等,那么就将窗口范围的所有数添加进结果集
            if(total == sum){
                ArrayList<Integer> list = new ArrayList<>();
                for(int i=left;i<=right;i++){
                    list.add(i);
                }
                left++;
                restList.add(list);
            //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
            }else if(total<sum){
                right++;
            //如果当前窗口内的值之和大于sum,那么左边窗口右移一下
            }else{
                left++;
            }
        }
        return restList;
    }
}
import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        //存放结果
        ArrayList<ArrayList<Integer>> restList = new ArrayList<>();
        if(sum <= 2){
            return restList;
        }
        
        for (int n = (int)Math.sqrt(2*sum); n >= 2; --n) {
            if(((n&1)==1 && sum%n==0) || ((sum%n*2)==n)){
                ArrayList<Integer> list = new ArrayList<Integer>();
                for(int j=0,k=(sum/n-(n-1)/2) ; j<n ;k++,j++){
                    list.add(k);
                }
                restList.add(list);
            }
        }
        
        return restList;
    }
}

第42题 数组:和为S的两个数字

1.题目:输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
2.考点:左右夹逼法。
3.解法:
假设:若b>a,且存在,a + b = s;(a - m ) + (b + m) = s则:(a - m )(b + m)=ab - (b-a)m - m*m < ab;说明外层的乘积更小。也就是说依然是左右夹逼法!!!只需要2个指针。
数列满足递增,设两个头尾两个指针i和j。
若ai + aj == sum,就是答案(相差越远乘积越小)。
若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1。
若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        int low =0;
        int high = array.length-1;
        ArrayList<Integer> list = new ArrayList<Integer>();
        while(low<high){
            if((array[low]+array[high]) == sum){
                list.add(array[low]);
                list.add(array[high]);
                return list;
            }else if((array[low]+array[high]) < sum){
                low++;
            }else{
                high--;
            }
        }
        return list;
    }
}

第43题 字符串:左旋转字符串

1.题目:汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
2.考点:YX = (XTY T)T。
3.解法:

public class Solution {
    public String LeftRotateString(String str,int n) {
        if(str.length() == 0){
            return str;
        }
        n = n%(str.length());
        //利用字符串的转置实现左旋转字符串
        char[] chars = str.toCharArray();
        reverse(chars,0,n-1);
        reverse(chars,n,str.length()-1);
        reverse(chars,0,str.length()-1);
        return new String(chars);
    }
    
    //转置
    public void reverse(char[] chars,int start,int end){
        while(start<end){
            swap(chars,start,end);
            start++;
            end--;
        }
    }
    
    //交换
    public void swap(char[] chars,int i,int j){
        char k = chars[i];
        chars[i]=chars[j];
        chars[j]=k;
    }
}

第44题 字符串:翻转单词顺序列

1.题目:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
2.考点:XYZ=(ZTYTXT)T。转置。
3.解法:
(1)两次反转,第一次单个单词反转,第二次整个反转。
题目应该有一个隐含条件,就是不能用额外的空间。虽然 Java 的题目输入参数为 String 类型,需要先创建一个字符数组使得空间复杂度为 O(N),但是正确的参数类型应该和原书一样,为字符数组,并且只能使用该字符数组的空间。任何使用了额外空间的解法在面试时都会大打折扣,包括递归解法。
正确的解法应该是和书上一样,先旋转每个单词,再旋转整个字符串。
(2)栈:首先每个字符串以次进栈: 栈顶hello栈底 world!然后依次出栈:hello world!。占用了额外的空间。

public class Solution {
    public String ReverseSentence(String str) {
        int len = str.length();
        if(len == 0){
            return str;
        }
        char[] chars = str.toCharArray();
        int i=0;
        int j=0;
        //先反转每一个词
        while(j<=len){
            if(j==len || chars[j]==' '){
                reverse(chars,i,j-1);
                i=j+1;
            }
            j++;
        }
        //再反转整个字符数组
        reverse(chars,0,len-1);
        return new String(chars);
    }
    
    //转置
    public void reverse(char[] chars,int start,int end){
        while(start<end){
            swap(chars,start,end);
            start++;
            end--;
        }
    }
    
    //交换
    public void swap(char[] chars,int i,int j){
        char k = chars[i];
        chars[i]=chars[j];
        chars[j]=k;
    }
}
(利用栈的代码有问题,没有调整)
import java.util.Stack;
public class Solution {
    public String ReverseSentence(String str) {
        Stack<String> Words = new Stack<>();
        int currBlankPos = 0;
        int perBlankPos = 0;
        String subString;
        while (currBlankPos>=0)
        {
            currBlankPos = str.indexOf(' ', perBlankPos); 
            // 找到空格分隔字符串(找到word压如栈里头)
            subString = str.substring(perBlankPos, currBlankPos < 0 ? 
            		(str.length() - perBlankPos) : currBlankPos - perBlankPos); 
            // 按长度截取单词
            perBlankPos = currBlankPos + 1;
            Words.push(subString); //把单词压入栈
        }
        subString="";
        while (!Words.empty())
        {
            subString += Words.peek(); Words.pop();
            if(!Words.empty()) subString += " "; // 需不需要加空格
        }
        return subString;
    }
}

第45题 数组:扑克牌顺子

1.题目:LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
2.考点:数组。
3.解法:
(1)1,排序 。2,计算所有相邻数字间隔总数 (每个间隔减1)。3,计算0的个数 。4,如果2和3相等,就是顺子 。5,如果出现对子,则不是顺子。
(2)1.max 记录 最大值。2.min 记录 最小值。3.min ,max 都不记0。4.满足条件 1 max - min <5。 除0外没有重复的数字(牌)。 5.数组长度 为5。

import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers.length < 5){
            return false;
        }
        //排序
        Arrays.sort(numbers);
        //计算大小王的数量
        int cnt0=0;
        for(int temp:numbers){
            if(temp==0){
                cnt0++;
            }
        }
        //使用大小王替换空值
        for(int i=cnt0;i<numbers.length-1;i++){
            if(numbers[i+1] == numbers[i]){
                return false;
            }else{
                cnt0 = cnt0 - (numbers[i+1]-numbers[i]-1);
            }
        }
        return cnt0>=0;
    }
}
import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        //如果数组为空或者长度小于5,直接返回false
        if(numbers.length < 5 || numbers==null){
            return false;
        }
        //排序
        Arrays.sort(numbers);
        //计算0的个数
        int cnt0=0;
        for(int i=0;i<numbers.length;i++){
            if(numbers[i]==0){
                cnt0++;
            }else{
                break;
            }
        }
        //如果最大值和最小值差大于等于5,则直接返回false
        if((numbers[numbers.length-1] - numbers[cnt0])>=5){
            return false;
        }
        //如果除了0,还有对子出现,则直接返回false
        for(int i=cnt0;i<numbers.length-1;i++){
            if(numbers[i+1]==numbers[i]){
                return false;
            }
        }
        //其他情况返回true;
        return true;
    }
}

第46题 链表:孩子们的游戏(圆圈中最后剩下的数)

1.题目:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)。如果没有小朋友,请返回-1
2.考点:约瑟夫环。 构建数组。链表。
3.解法:
(1)使用递归:约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈,所以最后需要对 n 取余。
首先定义最初的n个数字(0,1,…,n-1)中最后剩下的数字是关于n和m的方程为f(n,m)。在这n个数字中,第一个被删除的数字是(m-1)%n,为简单起见记为k。那么删除k之后的剩下n-1的数字为0,1,…,k-1,k+1,…,n-1,并且下一个开始计数的数字是k+1。相当于在剩下的序列中,k+1排到最前面,从而形成序列k+1,…,n-1,0,…k-1。该序列最后剩下的数字也应该是关于n和m的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从0开始的连续序列),因此该函数不同于前面函数,记为f’(n-1,m)。最初序列最后剩下的数字f(n,m)一定是剩下序列的最后剩下数字f’(n-1,m),所以f(n,m)=f’(n-1,m)。接下来我们把剩下的的这n-1个数字的序列k+1,…,n-1,0,…k-1作一个映射,映射的结果是形成一个从0到n-2的序列:
k+1 -> 0
k+2 -> 1

n-1 -> n-k-2
0 -> n-k-1

k-1 -> n-2
把映射定义为p,则p(x)= (x-k-1)%n,即如果映射前的数字是x,则映射后的数字是(x-k-1)%n。对应的逆映射是p-1(x)=(x+k+1)%n。由于映射之后的序列和最初的序列有同样的形式,都是从0开始的连续序列,因此仍然可以用函数f来表示,记为f(n-1,m)。根据我们的映射规则,映射之前的序列最后剩下的数字f’(n-1,m)= p-1 [f(n-1,m)]=[f(n-1,m)+k+1]%n。把k=(m-1)%n代入得到f(n,m)=f’(n-1,m)=[f(n-1,m)+m]%n。经过上面复杂的分析,我们终于找到一个递归的公式。要得到n个数字的序列的最后剩下的数字,只需要得到n-1个数字的序列的最后剩下的数字,并可以依此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0。我们把这种关系表示为:
在这里插入图片描述
尽管得到这个公式的分析过程非常复杂,但它用递归或者循环都很容易实现。最重要的是,这是一种时间复杂度为O(n),空间复杂度为O(1)的方法,因此无论在时间上还是空间上都优于前面的思路。
(2)不使用递归。
(3)构建数组。
(4)使用链表。

public class Solution {
    int count=0;
    public int LastRemaining_Solution(int n, int m) {
        if(n<=0){//特殊输入的处理
            return -1;
        }
        if(n==1){//递归返回的条件
            return 0;
        }
       return (LastRemaining_Solution(n-1, m)+m)%n;
    }
}
import java.util.ArrayList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<=0 || m<=0){//特殊输入的处理
            return -1;
        }
        /*
        new一个的长度是n,判断i等于n了,说明已经轮到最后一个完了,从0开始;
        判断a[i]是否等于-1,如果等于-1,说明这个人已经出局了,轮到下一个;
        step等于m,说明目前已经数到了,这个人该出局了;
        最后一个出局的人,也就是剩下的人.
        */
        //创建一个数组
        int[] arr = new int[n];
        int i=-1;
        int count = n;
        int step = -1;
        while(count>0){
            //数下一个人
            i++;
            if(i==n){
                i=0;
            }
            // a[i]=-1,表示要轮到的这个人已经出局了
            if(arr[i] == -1){
                continue;
            }
            step++;
            if(step == m-1){
                arr[i] = -1;
                step = -1;
                count--;
            }
        }
        return i;
    }
}
import java.util.ArrayList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<=0 || m<=0){//特殊输入的处理
            return -1;
        }
        ArrayList<Integer> list = new ArrayList<>();
        //遍历放入list
        for(int i=0;i<n;i++){
            list.add(i);
        }
        //开始找每次喊道m-1的位置,并删除
        int count = -1;//用于标记位置
        while(list.size() > 1){
            for(int i=0;i<m;i++){
                count++;
                if(count == list.size()){
                    count = 0;
                }
            }
            list.remove(count);
            count--;
            //cur--的原因,因为新的list中cur指向了下一个元素,
            //为了保证移动m个准确性,所以cur向前移动一位。
        }
        return list.get(0);
    }
}
import java.util.LinkedList;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        LinkedList<Integer> list = new LinkedList<Integer>();
        for (int i = 0; i < n; i ++) {
            list.add(i);
        }
         
        int bt = 0;
        while (list.size() > 1) {
            bt = (bt + m - 1) % list.size();
            list.remove(bt);
        }
         
        return list.size() == 1 ? list.get(0) : -1;
    }
}
public class Solution
{
    public int LastRemaining_Solution(int n, int m)
    {
        if(n==0||m==0)return -1;
        int s=0;
        for(int i=2;i<=n;i++)
        {
            s=(s+m)%i;//(LastRemaining_Solution(n-1, m)+m)%n;
        }   
       return s ;
    }
}

第47题 进制转换:求1+2+3+…+n

1.题目:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
2.考点:进制转换。条件与&&。
3.解法:
(1)短路与&& ,时间复杂度(o(n))。
使用递归解法最重要的是指定返回条件,但是本题无法直接使用 if 语句来指定返回条件。
条件与 && 具有短路原则,即在第一个条件语句为 false 的情况下不会去执行第二个条件语句。利用这一特性,将递归的返回条件取非然后作为 && 的第一个条件语句,递归的主体转换为第二个条件语句,那么当递归的返回条件为 true 的情况下就不会执行递归的主体部分,递归返回。
本题的递归返回条件为 n <= 0,取非后就是 n > 0;递归的主体部分为 sum += Sum_Solution(n - 1),转换为条件语句后就是 (sum += Sum_Solution(n - 1)) > 0。
(2)
共同点:一,利用利用短路 && 来实现 if的功能;二,利用递归来实现循环while的功能
不同点:方法一:递归实现1+2+…+n;方法二:n(n+1)/2,递归实现n(n+1);方法三,利用Math实现n(n+1)
关于如何递归实现a*b,有大佬总结过,我搬下来:利用位运算来做,快速幂,快速模乘,
原理是把a拆成2的幂的和,a = 2^e0 + 2^e1 + 2^e2… 。时间复杂度(o(logn))。
那么 a * b = (2^e0 + 2^e1 + 2^e2+…) * b
= b * 2^e0 + b * 2^e1 + b * 2^e2 + …
= (b << e0) + (b << e1) + …
在这里插入图片描述

public class Solution {
    public int Sum_Solution(int n) {
         int sum = 0;
        /*递归
        if(n>0){
            return n+Sum_Solution(n-1);
        }else{
            return 0;
        }
        */
        
        //由于不可以使用if,则需要重新设计递归中的判断条件
        //使用条件与&&
        boolean b = (n>0) && ((sum = n + Sum_Solution(n-1)) > 0);
        return sum;
    }
}
//方法3
//1.用到了while
public class Solution {    
    public int Sum_Solution(int n) {
        int sum = 0;
        //计算a*b sum=n*(n+1)/2
        int a = n;
        int b =n+1;
        while(a>0){
            if((a&1)==1){
                sum += b;
            }
            a=a>>1;
            b=b<<1;
        }
        return sum>>1;
    }
}

//2.接下来,用(a & 1) == 1和(a != 0)来代替判断语句
public class Solution {    
    public int Sum_Solution(int n) {
        int res = multi(n, n + 1);//n*(n-1)
        return res>>=1;//n*(n-1)/2
    }

    private int multi(int a, int b) {
        int res = 0;
        //循环体内部, if ((a & 1) == 1), res += b;
        boolean flag1 = ((a & 1) == 1) && (res += b) > 0;
        a >>= 1;
        b <<= 1;
        // while (a != 0) {}循环条件
        boolean flag2 = (a != 0) && (res += multi(a,b)) > 0 ;
        return res;
    }
}

第48题 进制转换:不用加减乘除做加法

1.题目:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
2.考点:位运算。
3.解法:a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位。递归会终止的原因是 (a & b) << 1 最右边会多一个 0,那么继续递归,进位最右边的 0 会慢慢增多,最后进位会变为 0,递归终止。
(1)非递归,会用到while循环。
(2)递归,值使用位运算。

public class Solution {
    public int Add(int num1,int num2) {
        //使用按位与或非
        int res = 0;
        while(num2 != 0){
            int temp = num1 ^ num2;
            num2 = (num2 & num1)<<1;
            num1 = temp;
        }
        return num1;
    }
}
public class Solution {
    public int Add(int num1,int num2) {
        return num2==0 ? num1:Add(num1 ^ num2,(num2 & num1)<<1);
    }
}

第49题 字符串:把字符串转换成整数

1.题目:将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
输入一个字符串,包括数字字母符号,可以为空。如果是合法的数值表达则返回该数字,否则返回0。
输入:+2147483647 1a33。输出:2147483647 0。
2.考点:
字符串。
注意int类型的变量a判断溢出,可以把a设置为long,然后判断a有没有在 0x7fffffff和 0x80000000L之间。
3.解法:

public class Solution {
    public int StrToInt(String str) {
        if(str==null || str.length()==0){
            return 0;
        }
        boolean isNegtive = (str.charAt(0) == '-');
        char c;
        long res = 0;
        for(int i=0;i<str.length();i++){
            c = str.charAt(i);
            if(i==0 && (c=='-' || c=='+')){//符号判断
                continue;
            }
            if(c<'0' || c>'9'){//非法判定
                return 0;
            }
            //溢出判断
            res = res*10 +(c-'0');//合法字符数字
            if(!isNegtive && res > 0x7fffffff
               || isNegtive && res > 0x80000000L) {
                return 0;
            }
        }
        return isNegtive ? -(int)res : (int)res;
    }
}

第50题 数组:数组中重复的数字

1.题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
2.考点:数组。
3.解法:
(1)利用boolean数组:由于数组中整形都大于0小于n-1,所以建立一boolean数组,分别用来存放[0,n-1]的数有没有出现在numbers数组中。初始时,都没有出现,首次出现时,标记为true,再次遇到这个标记为true时,即是数组中首个重复的数字。(以次推倒可以把数组中所有重的数字和索引都找出来)。【没有修改原数组】。时间复杂度(o(n))。需要额外的空间。
(2)不需要额外的存储:题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。【修改了原数组】。时间复杂度(o(n))。不需要额外的空间。
(3)利用HashSet的性质。时间复杂度(o(n))。需要额外的空间。
(4)时间复杂度(o(n))。不需要额外的空间。 思路:数组中的数字都在0到n-1的数字范围内。如果数组中没有重复出现的数字,那么当数组排序后数字i就出现在数组中下标为i的元素处。那么数组中如果存在重复数字的话,有些位置的对应的数字就没有出现,而有些位置可能存在多个数字。数组用numbers表示, 那么我们重排这个数组。从第0个元素开始。
1、比较numbers[i]和i的值,如果i与numbers[i]相等,也就是对数组排序后,numbers[i]就应该在对应的数组的第i个位置处,那么继续判断下一个位置。
2、如果i和numbers[i]的值不相等,那么判断以numbers[i]为下标的数组元素是什么。
2.1、如果numbers[numbers[i]]等于numbers[i]的话,那么就是说有两个相同的值了,重复了。找到了重复的数字
2.2、如果numbers[numbers[i]]不等于numbers[i]的话,那么就将numbers[numbers[i]]和numbers[i]互换。继续进行1的判断。
3、循环退出的条件是直至数组最后一个元素,仍没有找到重复的数字,数组中不存在重复的数字。
*/。

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: 
    //(Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null || length<=0){
            return false;
        } 
        //判断数组数据是否合法
        for(int i=0;i<length;i++){
            if(numbers[i]<0||numbers[i]>length-1){
                return false;
            }
        }
        boolean[] flag = new boolean[length];
        for(int i=0;i<length;i++){
            if(flag[numbers[i]] == true){
                duplication[0]=numbers[i];
                return true;
            }
            flag[numbers[i]]=true;
        }
        return false;
    }
}
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null || length<=0){
            return false;
        } 
         //判断数组数据是否合法
        for(int i=0;i<length;i++){
            if(numbers[i]<0||numbers[i]>length-1){
                return false;
            }
        }
        for(int i=0;i<length;i++){
            int index = numbers[i];
            if(index>=length){
                index -= length;
            }
            if(numbers[index]>=length){
                duplication[0] = numbers[index]-length;
                return true;
            }
            numbers[index]=numbers[index]+length;
        }
        return false;
    }
}
import java.util.HashSet;
public class Solution {
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null || length<=0){
            return false;
        } 
         //判断数组数据是否合法
        for(int i=0;i<length;i++){
            if(numbers[i]<0||numbers[i]>length-1){
                return false;
            }
        }
        HashSet<Integer> set = new HashSet<>();
        for(int i=0;i<length;i++){
            if(set.contains(numbers[i])){
                duplication[0]=numbers[i];
                return true;
            }
            set.add(numbers[i]);
        }
        return false;
    }
}
package Array;
public class Solution02 {
    public static void main(String[] args) {
        int[] arr ={2,3,1,0,2,5,3};
        int[] duplication = {-1};
        duplicate(arr,arr.length,duplication);
        System.out.println(duplication[0]);
    }
    public static boolean duplicate(int numbers[],int length,int [] duplication) {
        if(length<=0||numbers==null){
            return false;
        }
        //判断数组数据是否合法
        for(int i=0;i<length;i++){
            if(numbers[i]<0||numbers[i]>length-1){
                return false;
            }
        }
 
        for(int i=0;i<length;i++){
            while(numbers[i]!=i){
                if(numbers[i]==numbers[numbers[i]]){
                    duplication[0] = numbers[i];
                    return true;
                }else{
                    //交换numbers[i]和numbers[numbers[i]]
                    int temp = numbers[i];
                    numbers[i] = numbers[temp];
                    numbers[temp] = temp;
                }
            }
        }
        return false;
    }
}

第51题 数组:构建乘积数组

1.题目:给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。
2.考点:
3.解法:左乘和右乘。
(1)对于每一个B[i]都求出为左乘和右乘。但实际B[i]和B[i+1]的左边和右边是有关系的。因此方法重复计算了很多。
(2)考虑B[i]和B[i+1]的左边和右边之间的关系,减少重复性运算。
在这里插入图片描述

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int[] B = new int[A.length];
        for(int i=0;i<A.length;i++){
            B[i] = multi(0,i-1,A)*multi(i+1,A.length-1,A);
        }
        return B;
    }
    public int multi(int start,int end,int[] A){
        int num=1;
        if(start<=end){
            for(int i=start;i<=end;i++){
                num = num*A[i];
            }
        }
        return num;
    }
}
import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int n = A.length;
        int[] B = new int[n];
        if(n==0){
            return B;
        }
        //向右计算 从1到n-1
        /*
        #123...n-1
        0#23...n-1
        01#3...n-1
        ...
        0123...#
        */
        //向右做乘法
        for(int i=0,product=1;i<n;product*=A[i],i++){
            B[i]=product;
        }
        //向左做乘法
        for(int i=n-1,product=1;i>=0;product*=A[i],i--){
            B[i]*=product;
        }
        return B;
    }
}

第52题 字符串:正则表达式匹配

1.题目:请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"aba"均不匹配
2.考点:
3.解析:
/
首先,考虑特殊情况:
1>两个字符串都为空,返回true。
2>当第一个字符串不空,而第二个字符串空了,返回false(因为这样,就无法匹配成功了,而如果第一个字符串空了,第二个字符串非空,还是可能匹配成功的,比如第二个字符串是“aaaa”,由于‘’之前的元素可以出现0次,所以有可能匹配成功)之后就开始匹配第一个字符,这里有两种可能:匹配成功或匹配失败。但考虑到pattern下一个字符可能是‘’, 这里我们分两种情况讨论:pattern下一个字符为‘’或不为‘’: 1>pattern下一个字符不为‘’:这种情况比较简单,直接匹配当前字符。如果 匹配成功,继续匹配下一个;如果匹配失败,直接返回false。注意这里的 “匹配成功”,除了两个字符相同的情况外,还有一种情况,就是pattern的 当前字符为‘.’,同时str的当前字符不为‘\0’。 2>pattern下一个字符为‘’时,稍微复杂一些,因为‘’可以代表0个或多个。这里把这些情况都考虑到:
a>当‘
’匹配0个字符时,str当前字符不变,pattern当前字符后移两位,跳过这个‘’符号;
b>当‘
’匹配1个或多个时,str当前字符移向下一个,pattern当前字符不变。(这里匹配1个或多个可以看成一种情况,因为:当匹配一个时,由于str移到了下一个字符,而pattern字符不变,就回到了上边的情况a;当匹配多于一个字符时,相当于从str的下一个字符继续开始匹配)*/

public class Solution {
    public boolean match(char[] str, char[] pattern){
        if(str==null || pattern==null){
            return false;
            
        }
        return match(str,0,pattern,0);
    }
    public boolean match(char[] str,int strIndex,char[] pattern,
                            int patternIndex){
        //有效性检验
        if(strIndex==str.length && patternIndex==pattern.length){
            return true;
        }
        if(patternIndex==pattern.length && strIndex!=str.length){
            return false;
        }
        //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;
        if((patternIndex+1)<pattern.length && pattern[patternIndex+1]=='*'){
            if((strIndex<str.length && str[strIndex]==pattern[patternIndex])
             || (strIndex<str.length && pattern[patternIndex]=='.')) {
                //模式后移2,视为x*匹配0个字符
                return match(str,strIndex,pattern,patternIndex+2)
                //*匹配1个,再匹配str中的下一个
                      || match(str,strIndex+1,pattern,patternIndex);
            }else{//如不匹配,模式后移2位
                       return match(str,strIndex,pattern,patternIndex+2);
            }
        }
 //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
        if((strIndex<str.length && str[strIndex]==pattern[patternIndex]) || 
                  (strIndex<str.length && pattern[patternIndex]=='.')) {
                      return match(str,strIndex+1,pattern,patternIndex+1);
        }
        return false;
    }
}
public boolean match(char[] str, char[] pattern) {
    int m = str.length, n = pattern.length;
    boolean[][] dp = new boolean[m + 1][n + 1];

    dp[0][0] = true;
    for (int i = 1; i <= n; i++)
        if (pattern[i - 1] == '*')
            dp[0][i] = dp[0][i - 2];

    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.')
                dp[i][j] = dp[i - 1][j - 1];
            else if (pattern[j - 1] == '*')
                if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') {
                    dp[i][j] |= dp[i][j - 1];
                     // a* counts as single a
                    dp[i][j] |= dp[i - 1][j];
                     // a* counts as multiple a
                    dp[i][j] |= dp[i][j - 2];
                     // a* counts as empty
                } else
                    dp[i][j] = dp[i][j - 2];   
                    // a* only counts as empty

    return dp[m][n];
}

第53题 字符串:表示数值的字符串

1.题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是 “12e”,“1a3.14”,“1.2.3”,“±5” 和"12e+4.3"都不是。
2.考点:正则表达式。
3.解析:
(1)正则表达式。
在这里插入图片描述(2)自己编写代码。

/*
以下对正则进行解释:
[\\+\\-]?            -> 正或负符号出现与否
\\d*                 -> 整数部分是否出现,如-.34 或 +3.34均符合
(\\.\\d+)?           -> 如果出现小数点,那么小数点后面必须有数字;
                        否则一起不出现
(\\.)?\\d*           -> 如果出现小数点,那么小数点后面可以有数字,也可以没有数字;
([eE][\\+\\-]?\\d+)? -> 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,
                        紧接着必须跟着整数;或者整个部分都不出现
*/
public class Solution {
    public boolean isNumeric(char[] str) {
        if(str==null || str.length==0){
            return false;
        }
        return new String(str).matches("[+-]?\\d*(\\.)?\\d*([Ee][+-]?\\d+)?");
    }
}
public class Solution {
    private int index = 0;
    public boolean isNumeric(char[] str) {
        if (str==null || str.length==0) return false;
        boolean flag = scanInteger(str);
        // 如果出现'.',接下来是数字的小数部分
        // 下面一行代码用||的原因:
        // 1. 小数可以没有整数部分,例如.123等于0.123;
        // 2. 小数点后面可以没有数字,例如233.等于233.0;
        // 3. 当然小数点前面和后面可以有数字,例如233.666
        if (index < str.length && str[index] == '.') {
            index++;
            flag = scanUnsignedInteger(str) || flag;
        }
        // 如果出现'e'或者'E',接下来跟着的是数字的指数部分
        // 下面一行代码用&&的原因:
        // 1. 当e或E前面没有数字时,整个字符串不能表示数字,例如.e1、e1;
        // 2. 当e或E后面没有整数时,整个字符串不能表示数字,例如12e、12e+5.4
        if (index < str.length && (str[index] == 'E' || str[index] == 'e')) {
            index++;
            flag = flag && scanInteger(str);
        }
        return flag && index == str.length;
    }

    private boolean scanInteger(char[] str) {
        if (index < str.length && (str[index] == '+' || str[index] == '-') )
            index++;
        return scanUnsignedInteger(str);
    }
    private boolean scanUnsignedInteger(char[] str) {
        int start = index;
        while (index < str.length && str[index] >= '0' && str[index] <= '9')
            index++;
        // 当str中存在若干0-9的数字时,返回true
        return start < index; 
    }
}

第54题 字符串:字符串中第一个不重复的字符

1.题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
2.考点:
3.解析:
(1)使用队列和数组。
(2)使用LinkedHashMap。HashMap是无序的。LinkedHashMap是有序的,且默认为插入顺序。

import java.util.LinkedList;
import java.util.Queue;
public class Solution {
    int[] count = new int[256];
    Queue<Character> queue = new LinkedList<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        count[ch]++;
        queue.add(ch);
        while(!queue.isEmpty() && count[queue.peek()]>1){
            queue.poll();
        }
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        char ret = '#';
        if(!queue.isEmpty()){
            ret = queue.peek();
        }
        return ret;
    }
}
import java.util.HashMap;
import java.util.LinkedHashMap;
public class Solution {
    HashMap<Character,Integer> map = new LinkedHashMap<Character,Integer>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(map.containsKey(ch)){
            map.put(ch,(map.get(ch))+1);
        }else{
            map.put(ch,1);
        }
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(Character c:map.keySet()){
            if(map.get(c)==1){
                return c;
            }
        }
        return '#';
    }
}

第55题 链表:链表中环的入口点

1.题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
2.考点:
3.解法:
(1)不修改链表,使用快慢指针的方法。
在这里插入图片描述

假设x为环前面的路程(黑色路程),a为环入口到相遇点的路程(蓝色路程,假设顺时针走), c为环的长度(蓝色+橙色路程)
当快慢指针相遇的时候:
此时慢指针走的路程为Sslow = x + m * c + a
快指针走的路程为Sfast = x + n * c + a
2 Sslow = Sfast
2 * ( x + m*c + a ) = (x + n *c + a)
从而可以推导出:
x = (n - 2 * m )*c - a
= (n - 2 *m -1 )*c + c - a
即环前面的路程 = 数个环的长度(为可能为0) + c - a。什么是c - a?这是相遇点后,环后面部分的路程。(橙色路程)
所以,我们可以让一个指针从起点A开始走,让一个指针从相遇点B开始继续往后走,2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x)从相遇点开始走的那个指针也一定刚好到达环入口点。所以2者会相遇,且恰好相遇在环的入口点。
最后,判断是否有环,且找环的算法复杂度为:时间复杂度:O(n),空间复杂度:O(1)。
(2)断链法:修改链表,使用快慢指针的方法。
(3)使用哈希表。HashSet和HashMap

/*
 public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null || pHead.next==null || pHead.next.next==null){
            return null;
        }
        ListNode low = pHead.next;
        ListNode fast = pHead.next.next;
        //寻找快慢指针的相遇点
        while(low != fast){
            if(fast.next!=null && fast.next.next!=null){
                fast = fast.next.next;
                low = low.next;
            }else{
                return null;
            }
        }
        //寻找环的入口点
        low = pHead;
        while(low != fast){
            low = low.next;
            fast = fast.next;
        }
        return low;
    }
}
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null || pHead.next==null || pHead.next.next==null){
            return null;
        }
        ListNode low = pHead;
        ListNode fast = pHead.next;
        ListNode node = new ListNode(1);
        while(fast != node){
            if(fast == null){
                return null;
            }
            low.next = node;
            low = fast;
            fast = fast.next;
        }
        return low;
    }
}
import java.util.HashSet;
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null){
            return null;
        }
        HashSet<ListNode> set = new HashSet<>();
        while(pHead!=null){
            if(!set.add(pHead)){
                return pHead;
            }
            pHead = pHead.next;
        }
        return null;
    }
}
import java.util.HashMap;
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead==null){
            return null;
        }
        HashMap<ListNode,Boolean> set = new HashMap<>();
        while(pHead!=null){
            if(set.containsKey(pHead)){
                return pHead;
            }
            set.put(pHead,true);
            pHead = pHead.next;
        }
        return null;
    }
}

第56题 链表:删除链表中重复的节点

1.题目:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
2.考点:
3.解法:
(1)非递归的方法:1. 首先添加一个头节点,以方便碰到第一个,第二个节点就相同的情况。2.设置 pre ,last 指针, pre指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索。
(2)递归的方法:

/*
 public class ListNode {
    int val;
    ListNode next = null;
    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null || pHead.next== null){
            return pHead;
        }
        //构建一个头节点
        ListNode head = new ListNode(-1);
        head.next=pHead;
        ListNode pre = head;
        ListNode last = head.next;
        while(last != null){
            if(last.next!=null && last.val == last.next.val){
                 // 找到最后的一个相同节点
                while(last.next!=null && (last.next.val == last.val)){
                    last = last.next;
                }
                pre.next = last.next;
                last = last.next;
            }else{
                pre = pre.next;
                last = last.next;
            }
        }
        return head.next;
    }
}
public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        // 只有0个或1个结点,则返回
        if (pHead == null || pHead.next == null) { 
            return pHead;
        }
        if (pHead.val == pHead.next.val) { // 当前结点是重复结点
            ListNode pNode = pHead.next;
            while (pNode != null && pNode.val == pHead.val) {
            // 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点
                pNode = pNode.next;
            }
            // 从第一个与当前结点不同的结点开始递归
            return deleteDuplication(pNode);
        } else { // 当前结点不是重复结点
          // 保留当前结点,从下一个结点开始递归
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }
}

第57题 二叉树:二叉树的下一个节点

1.题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
2.考点:
3.解法:/分析二叉树的下一个节点,一共有以下情况://1.二叉树为空,则返回空;//2.节点右孩子存在,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;//3.节点不是根节点。如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,重复之前的判断,返回结果。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;
    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode==null){
            return null;
        } 
        //如果当前节点有左子节点,则下一个节点是左子树的最左子节点
        if(pNode.right != null){
            pNode = pNode.right;
            while(pNode.left!=null){
                pNode = pNode.left;
            }
            return pNode;
        //如果当前节点没有右子节点
        //1.当前节点是其父节点的左子结点,则下一个节点是其父节点
        //2.当前节点不是其父节点的左子结点,
         //则下一个节点是找到其父节点的父节点,,,知道某个节点是其父节点的左子结点,
         //则下一个节点是,该节点的父节点
        }else{
            while(pNode.next != null){
                if( pNode==pNode.next.left){
                    return pNode.next;
                }
                pNode = pNode.next;
            }
        }
        return null;
    }
}

第58题 二叉树:对称的二叉树

1.题目:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
2.考点:
3.解法:
(1)递归:1.只要pRoot.left和pRoot.right是否对称即可。2.左右节点的值相等且对称子树left.left, right.right ;left.rigth,right.left也对称
(2)非递归,使用栈:* DFS使用stack来保存成对的节点。* 1.出栈的时候也是成对成对的 ,1.若都为空,继续;2.一个为空,返回false;3.不为空,比较当前值,值不等,返回false;* 2.确定入栈顺序,每次入栈都是成对成对的,如left.left, right.right ;left.rigth,right.left。【栈先进后出】
(3)非递归,使用队列。* BFS使用Queue来保存成对的节点,代码和上面极其相似。* 1.出队的时候也是成对成对的1.若都为空,继续;2.一个为空,返回false;3.不为空,比较当前值,值不等,返回false;* 2.确定入队顺序,每次入队都是成对成对的,如left.left, right.right ;left.rigth,right.left。【队列先进先出】【在二叉树的层次遍历中也是使用的队列】
(4)自己的想法:建立数组,使用中序边遍历,代码没写出来。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot == null){
            return true;
        }
        return isSymmetrical(pRoot.left,pRoot.right);
    }
    boolean isSymmetrical(TreeNode left,TreeNode right){
        if(left==null && right==null){
            return true;
        }
        if(left==null || right==null){
            return false;
        }
        return (left.val==right.val) && isSymmetrical(left.left,right.right) 
                    && isSymmetrical(left.right,right.left);
    }
}
import java.util.Stack;
public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot == null){
            return true;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(pRoot.left);
        stack.push(pRoot.right);
        while(!stack.isEmpty()){
            //成对取出
            TreeNode right = stack.pop();
            TreeNode left = stack.pop();
            if(left==null && right==null){
                continue;
            }
            if(left==null || right==null){
                return false;
            }
            if(left.val != right.val){
                return false;
            }
            //成对加入
            stack.push(left.left);
            stack.push(right.right);
            stack.push(left.right);
            stack.push(right.left);
        }
        return true;
    }
}
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
    {
        if(pRoot == null){
            return true;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot.left);
        queue.offer(pRoot.right);
        while(!queue.isEmpty()){
            //成对取出
            TreeNode left = queue.poll();
            TreeNode right = queue.poll();
            if(left==null && right==null){
                continue;
            }
            if(left==null || right==null){
                return false;
            }
            if(left.val != right.val){
                return false;
            }
            //成对加入
            queue.offer(left.left);
            queue.offer(right.right);
            queue.offer(left.right);
            queue.offer(right.left);
        }
        return true;
    }
}

第59题 二叉树:按之字形顺序打印二叉树

1.题目:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
2.考点:
3.解法:
(1)使用reverse。
(2)不使用reverse,大家的实现很多都是将每层的数据存进ArrayList中,偶数层时进行reverse操作, * 在海量数据时,这样效率太低了。 * (我有一次面试,算法考的就是之字形打印二叉树,用了reverse, * 直接被鄙视了,面试官说海量数据时效率根本就不行)。 * * 下面的实现:不必将每层的数据存进ArrayList中,偶数层时进行reverse操作,直接按打印顺序存入。使用栈两个。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.ArrayList;
import java.util.Queue;
import java.util.Collections;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> listAll = new ArrayList<>();
        if(pRoot == null){
            return listAll;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        boolean isReverse = false;
        while(!queue.isEmpty()){
            ArrayList<Integer> list = new ArrayList<>();
            int cnt = queue.size();
            while(cnt-->0){
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left!=null){
                    queue.offer(node.left);
                }
                if(node.right!=null){
                    queue.offer(node.right);
                }
            }
            if(isReverse)
                Collections.reverse(list);
            isReverse = !isReverse;
            if(list.size() != 0)
                listAll.add(list);
        }
        return listAll;
    }
}
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> listAll = new ArrayList<>();
        if(pRoot == null){
            return listAll;
        }
        Stack<TreeNode> stack1 = new Stack<>();
        stack1.push(pRoot);
        Stack<TreeNode> stack2 = new Stack<>();
        boolean flag = true;//从左向右
        while(!stack1.isEmpty() || !stack2.isEmpty()){
            if(flag){
                ArrayList<Integer> list = new ArrayList<>();
                while(!stack1.isEmpty()){
                    TreeNode temp = stack1.pop();
                    list.add(temp.val);
                    if(temp.left!=null){
                        stack2.push(temp.left);
                    }
                    if(temp.right!=null){
                        stack2.push(temp.right);
                    }
                } 
                if(!list.isEmpty()){
                    listAll.add(list);
                    flag = !flag;
                }
            }else{
                ArrayList<Integer> list = new ArrayList<>();
                while(!stack2.isEmpty()){
                    TreeNode temp = stack2.pop();
                    list.add(temp.val);
                    if(temp.right!=null){
                        stack1.push(temp.right);
                    }
                    if(temp.left!=null){
                        stack1.push(temp.left);
                    }
                }
                if(!list.isEmpty()){
                    listAll.add(list);
                    flag = !flag;
                }
            }
        }
        return listAll;
    }
}

第60题 二叉树:把二叉树打印成多行

1.题目:从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
2.考点:二叉树的按层次遍历。
3.解法:
(1)不使用递归,使用队列。
(2)使用递归,使用队列。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> listAll = new ArrayList<>();
        if(pRoot == null){
            return listAll;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(pRoot);
        while(!queue.isEmpty()){
            ArrayList<Integer> list = new ArrayList<>();
            int cnt = queue.size();
            while(cnt-->0){
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left!=null){
                    queue.offer(node.left);
                }
                if(node.right!=null){
                    queue.offer(node.right);
                }
            }
            if(list.size() != 0)
                listAll.add(list);
        }
        return listAll;
    }
}

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
	ArrayList<ArrayList<Integer>> listAll = new ArrayList<>();
        depth(pRoot,1,listAll);
        return listAll;
    }
    public void depth(TreeNode node,int depth,ArrayList<ArrayList<Integer>> listAll){
        if(node==null){
            return;
        }
        
        if(depth>listAll.size()){
            listAll.add(new ArrayList<Integer>());
        }
        listAll.get(depth-1).add(node.val);
        
        depth(node.left,depth+1,listAll);
        depth(node.right,depth+1,listAll);
    }
}

第61题 二叉树:序列化二叉树

1.题目:请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
2.考点:
3.解法:

public class Solution {
    //先序
    String str1;
    String Serialize(TreeNode root) {
        if(root==null)
            return "#";
        //1!2!3!4!#!6(最后一个数没有!)
        return root.val+"!"+Serialize(root.left)+"!"+Serialize(root.right);
  }
    TreeNode Deserialize(String str) {
        this.str1 = str;
       if(str1.length()==0){
           return null;
       }
        int index = str1.indexOf("!");
        String s = index==-1 ? str1 : str1.substring(0,index);
        str1 = index==-1 ? "" : str1.substring(index+1);
        if(s.equals("#"))
            return null;
        TreeNode node = new TreeNode(Integer.valueOf(s));
        node.left = Deserialize(str1);
        node.right = Deserialize(str1);
        return node;
  }
}

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//先序遍历.
public class Solution {
    String strCopy;
    String Serialize(TreeNode root) {
        if(root == null){
            return "#";
        }
        return root.val+"!"+Serialize(root.left)+"!"+Serialize(root.right);
        
  }
    TreeNode Deserialize(String str) {
        strCopy = str;
        return  Deserialize();
    }
    private TreeNode Deserialize() {
        if(strCopy.length()==0){
            return null;
        }
        int index = strCopy.indexOf("!");
        String node = index==-1 ? strCopy : strCopy.substring(0,index);
        strCopy = index==-1 ? "" : strCopy.substring(index+1);
        if(node .equals("#")){
            return null;
        }
        TreeNode t = new TreeNode(Integer.valueOf(node));
        t.left =  Deserialize();
        t.right = Deserialize();
        return t;
    }
}

第62题 二叉树:二叉搜索树的第k个节点

1.题目:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
2.考点:
3.解法:
(1)利用二叉查找树中序遍历有序的特点,递归。
(2)利用栈,非递归。

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//二叉树的中须遍历的第k个值
public class Solution {
    TreeNode node1 = null;
    int cnt =0;
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        midSearch(pRoot,k);
        return node1;
    }
    public void midSearch(TreeNode node,int k){
        if(node==null || k<=0){
            return;
        }
        midSearch(node.left,k);
        cnt++;
        if(k==cnt){
            node1 = node;
            return ;
        }
        midSearch(node.right,k);
    }
}
import java.util.Stack;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        int cnt = 0;
        Stack<TreeNode> stack = new Stack<>();
        while(pRoot!= null || !stack.isEmpty()){
            if(pRoot!=null){
                stack.push(pRoot);
                pRoot = pRoot.left;
            }else{
                pRoot = stack.pop();
                cnt++;
                if(cnt==k){
                    return pRoot;
                }
                pRoot = pRoot.right;
            }
        }
        return null;
    }   
}

第63题:进制转换:数据流中的中位数

1.题目:如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
2.考点:大顶堆,小顶堆。
3.解法:
(1)直接使用大小顶堆。
先用java集合PriorityQueue来设置一个小顶堆和大顶堆。主要的思想是:因为要求的是中位数,那么这两个堆,大顶堆用来存较小的数,从大到小排列;小顶堆存较大的数,从小到大的顺序排序*,显然中位数就是大顶堆的根节点与小顶堆的根节点和的平均数。
⭐保证:小顶堆中的元素都大于等于大顶堆中的元素,所以每次塞值,并不是直接塞进去,而是从另一个堆中poll出一个最大(最小)的塞值
⭐当数目为偶数的时候,将这个值插入大顶堆中,再将大顶堆中根节点(即最大值)插入到小顶堆中;
⭐当数目为奇数的时候,将这个值插入小顶堆中,再讲小顶堆中根节点(即最小值)插入到大顶堆中;
⭐取中位数的时候,如果当前个数为偶数,显然是取小顶堆和大顶堆根结点的平均值;如果当前个数为奇数,显然是取小顶堆的根节点
(2)自己实现大小顶堆。
思路:构建一棵"平衡二叉搜索树 "。每个结点左子树均是小于等于其value的值,右子树均大于等于value值。每个子树均按其 “结点数” 调节平衡。 这样根节点一定是中间值中的一个。若结点数为奇数,则返回根节点的值;若结点个数为偶数,则再从根结点左子数或右子数中个数较多的子树中选出最大或最小值既可。
(3)使用LinkedList。

import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
    /* 大顶堆,存储左半边元素 */
    private PriorityQueue<Integer> left = 
            new PriorityQueue<>((o1, o2) -> o2 - o1);
    /* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
    private PriorityQueue<Integer> right = new PriorityQueue<>();
    /* 当前数据流读入的元素个数 */
    private int N = 0;
    
    public void Insert(Integer val) {
        /* 插入要保证两个堆存于平衡状态 */
        if (N % 2 == 0) {
            /* N 为偶数的情况下插入到右半边。
 * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大,
 * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,
     取出堆顶元素即为最大元素,此时插入右半边 */
            left.add(val);
            right.add(left.poll());
        } else {
            right.add(val);
            left.add(right.poll());
        }
        N++;
    }

    public Double GetMedian() {
        if (N % 2 == 0)
            return (left.peek() + right.peek()) / 2.0;
        else
            return (double) right.peek();
    }
}
import java.util.LinkedList;
public class Solution {
    LinkedList<Integer> list = new LinkedList<Integer>();
 
    public void Insert(Integer num) {
        if (list.size()==0||num < list.getFirst()) {
            list.addFirst(num);
        } else {
            boolean insertFlag = false;
            for (Integer e : list) {
                if (num < e) {
                    int index = list.indexOf(e);
                    list.add(index, num);
                    insertFlag = true;
                    break;
                }
            }
            if (!insertFlag) {
                list.addLast(num);
            }
        } 
    }
 
    public Double GetMedian() {
        if (list.size() == 0) {
            return null;
        }
        if (list.size() % 2 == 0) {
            int i = list.size() / 2;
            Double a = Double.valueOf(list.get(i - 1) + list.get(i));
            return a / 2;
        }
        list.get(0);
        Double b = Double.valueOf(list.get((list.size() + 1) / 2 - 1));
        return Double.valueOf(list.get((list.size() + 1) / 2 - 1)); 
    }
}

第64题 数组:滑动窗口的最大值

1.题目:给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
2.考点:
3.解法:
(1)数组。时间复杂度(o((n-size)*size))。
(2)大顶堆。时间复杂度(o(n))?。
(3)双端队列。时间复杂度(o(n))?。这道题可以用双向队列解决(就是头尾都可以push,pop的队列)
时间复杂度 O(N)方法如下几点: 1.当我们遇到新数时候,将新的数和双向队列的末尾比较,若果末尾比新数小,则把末尾pop_back, 直到末尾比新数大或者队列为空才停止;2.这样就保证队列元素是从头到尾降序的,由于队列里只有窗口内的数,所以它们其实是窗口内第一大,第二大…的数。 3.保持队列里只有窗口大小的数的方法是, 右边进一个,左边出一个。 4.由于队列加新数时,已经删除了部分没用的数, 所以队列头的数并不一定是窗口最左的数, 这里有个小技巧,就是队列中存原数组的下标, 这样既可以得到这个数,也可以判断是否是窗口左边的数了。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> list = new ArrayList<>();
        if(num==null || (num.length<size || size<=0)){
            return list;
        }
        int max;
        for(int i=0;i<=(num.length-size);i++){
            max = num[i];
            for(int j=i+1;j<(i+size);j++){
                if(num[j]>max){
                    max = num[j];
                }
            }
            list.add(max);
        }
        return list;
    }
}
import java.util.ArrayList;
import java.util.PriorityQueue;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> list = new ArrayList<>();
        if(num==null || (num.length<size || size<=0)){
            return list;
        }
        //大顶堆
        PriorityQueue<Integer> heap = new PriorityQueue<>((o1,o2) -> o2-o1);
        for(int i=0;i<size;i++){
            heap.add(num[i]);
        }
        list.add(heap.peek());
        for(int i=0,j=size;j<num.length;i++,j++){
            heap.remove(num[i]);
            heap.add(num[j]);
            list.add(heap.peek());
        }
        return list;
    }
}
import java.util.ArrayList;
import java.util.LinkedList;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> list = new ArrayList<>();
        if(num==null ||num.length==0|| num.length<size || size<=0){
            return list;
        }
        //双端队列
        //双端队列,用来记录每个窗口的最大值下标 从大到小【对头到队尾】
        LinkedList<Integer> queue = new LinkedList<Integer>();
        for(int i=0;i<num.length;i++){
            while(!queue.isEmpty() && (num[queue.peekLast()]<num[i])){
                queue.pollLast();
            }
            queue.addLast(i);
            //判断队首元素是否过期
            if(queue.peekFirst() <= i-size){
                queue.pollFirst();
            }
            //向list列表中加入元素
            if(i>=size-1){
                list.add(num[queue.peekFirst()]);
            }
        }
        return list;
    }
}

第65题 矩阵:矩阵中的路径

1.题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
2.考点:回溯法。
3.解法:
使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。例如下图示例中,从 f 开始,下一步有 4 种搜索可能,如果先搜索 b,需要将 b 标记为已经使用,防止重复使用。在这一次搜索结束之后,需要将 b 的已经使用状态清除,并搜索 c。例如下面的矩阵包含了一条 bfce 路径。
在这里插入图片描述

public class Solution {
    int rows;
    int cols;
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix==null || str==null || matrix.length==0 || str.length==0){
            return false;
        }
        this.rows = rows;
        this.cols = cols;
        boolean[] marked = new boolean[matrix.length];
        //对于每个位置调用回溯方法
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(backTracking(matrix,str,marked,0,i,j)){
                    return true;
                }
            }
        }
        return false;
    }
    
    //回溯
    public boolean backTracking(char[] matrix,char[] str,
                                 boolean[] marked,int pathLen,int r,int c){
        int index =  r*cols +c;
        if(pathLen == str.length){
            return true;
        }
        if(r<0 || r>=rows || c<0 || c>=cols 
                || matrix[index]!=str[pathLen] || marked[index]){
            return false;
        }
        marked[index] = true;
        if(backTracking(matrix,str,marked,pathLen+1,r,c-1)){
            return true;
        }
        if(backTracking(matrix,str,marked,pathLen+1,r,c+1)){
            return true;
        }
        if(backTracking(matrix,str,marked,pathLen+1,r-1,c)){
            return true;
        }
        if(backTracking(matrix,str,marked,pathLen+1,r+1,c)){
            return true;
        }
        marked[index] =false;
        return false;
    }
}
public class Solution {
    int rows;
    int cols;
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix==null || str==null || matrix.length==0 || str.length==0){
            return false;
        }
        this.rows = rows;
        this.cols = cols;
        boolean[][] marked = new boolean[rows][cols];
        //先把一维数组转化为二维数组
        char[][] arr = new char[rows][cols];
        int k=0;
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                arr[i][j] = matrix[k];
                k++;
            }
        }
        //对于每个位置调用回溯方法
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(backTracking(arr,str,marked,0,i,j)){
                    return true;
                }
            }
        }
        return false;
    }
    
    //回溯
    public boolean backTracking(char[][] arr,char[] str,
                                boolean[][] marked,int pathLen,int r,int c){
        if(pathLen == str.length){
            return true;
        }
        if(r<0 || r>=rows || c<0 || c>=cols 
                || arr[r][c]!=str[pathLen] || marked[r][c]){
            return false;
        }
        marked[r][c] = true;
        if(backTracking(arr,str,marked,pathLen+1,r,c-1)){
            return true;
        }
        if(backTracking(arr,str,marked,pathLen+1,r,c+1)){
            return true;
        }
        if(backTracking(arr,str,marked,pathLen+1,r-1,c)){
            return true;
        }
        if(backTracking(arr,str,marked,pathLen+1,r+1,c)){
            return true;
        }
        marked[r][c] =false;
        return false;
    }
}

第66题 数组:机器人的运动范畴

1.题目:地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
2.考点:回溯法。
3.解法:
(1)使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。而普通的深度优先搜索并不需要使用这些局部状态,虽然还是有可能设置一些全局状态。

public class Solution {  
    int rows;
    int cols;
    int threshold;
    int[][] digitSum;
    int count = 0;
    public int movingCount(int threshold, int rows, int cols)
    {
        this.rows = rows;
        this.cols = cols;
        this.threshold = threshold;
        boolean[][] marked = new boolean[rows][cols];
        initDigitSum();
        dfs(marked,0,0);
        return count;
    }
    public void dfs(boolean[][] marked,int r,int c){
        if(r>=rows || r<0 || c>=cols || c<0 || marked[r][c]){
            return ;
        }
        marked[r][c] = true;
        if(this.digitSum[r][c]>this.threshold){
            return;
        }
        count++;
        dfs(marked,r,c-1);
        dfs(marked,r,c+1);
        dfs(marked,r-1,c);
        dfs(marked,r+1,c);
    }
    public void initDigitSum(){
        int[] arr = new int[Math.max(rows,cols)];
        for(int i=0;i<arr.length;i++){
            int j=i;
            while(j>0){
                arr[i]+=j%10;
                j=j/10;
            }
        }
        this.digitSum = new int[rows][cols];
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                digitSum[i][j] = arr[i]+arr[j];
            }
        }
    }
}

第67题 贪心算法:剪绳子

1.题目:给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入一个数n,意义见题面。(2 <= n <= 60)
输出答案。输入8;输出18。
n = 2 ;return 1 (2 = 1 + 1)
n = 10;return 36 (10 = 3 + 3 + 4)
2.考点:
3.解法:
(1)贪心法:1.由于是乘法,那么除了一以外越多数相乘,得到的结果就越大。2.因此从2开始考虑。但是都分成2的话必然会有奇数分成包含1的段数,因为1相当于浪费了一个乘数,所以如果最后剩1的时候我们应该将他变为3. 因此得到分成的段数长度为2,3是最好的。3.又因为 2 * 2 * 2 < 3 * 3 说明3个2都不如2个3 ,因此应该让3 相对来说比2 多。4.于是让该数对3相除,余数如果为2,则分为 1个2 ,N个3 为最优解,如果余数为1,则应分为2个2 ,N-1 个3 为最优解,因此得出以下代码
(2)动态规划:

public class Solution {
    public int cutRope(int target) {
        if(target<2){
            return 0;
        }else if(target==2){
            return 1;
        }else if(target==3){
            return 2;
        }else{//target>=4时
            int numof3 = (target%3==1) ? (target/3-1):target/3;
            int numof2 = (target - 3*numof3)/2;
            return (int)Math.pow(3,numof3)*(int)Math.pow(2,numof2);
        }
    }
}
public class Solution {
    public int cutRope(int target) {
        if(target <= 1){
            return 0;
        }
        int[] ret = new int[target+1];
        ret[1]=1;
        for(int i=2;i<=target;i++){
            for(int j=1;j<i;j++){
                ret[i]=Math.max(ret[i],Math.max(j*(i-j),ret[j]*(i-j)));
            }
        }
        return ret[target];
    }
}
public class Solution {
    public int cutRope(int target) {
        int max=0;
        int m=2;
        int res=1;
        while(max<res){
            max=res;
            int length=(int)Math.ceil((double)target/m);
            int left=target%length;
            int num=target/length;
            res=(int)Math.pow(length,num)*(left==0?1:left);
            m++;
        }
        return max;
    }
}   

Note:部分解答整合了CS-Notes和牛客讨论区中的内容,如有不妥,请联系本人删除。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值