剑指offer

2. 单例设计模式
题目描述:设计一个类,只能生成该类的一个实例。
思路:非线程安全与线程安全
**所谓单例设计模式简单说就是无论程序如何运行,采用单例设计模式的类(Singleton类)永远只会有一个实例化对象产生。**具体实现步骤如下:
(1) 将采用单例设计模式的类的构造方法私有化(采用private修饰)。
(2) 在其内部产生该类的实例化对象,并将其封装成private static类型。
(3) 定义一个静态方法返回该类的实例。
https://www.cnblogs.com/tytr/p/6119573.html
#这种实现方式线程安全且效率比较高。

public class Singleton {   
 private static Singleton ourInstance;   
  public static Singleton getInstance() {       
   if (null == ourInstance) {            
   synchronized (Singleton.class) {                             
     ourInstance = new Singleton();              
      }        }
       return ourInstance;    
       }    
       private Singleton() {} }

3. 二维数组中查找目标值
题目:https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:
每次将二维数组矩阵的中最右上角的数字与要查找的数字比较,基于二维数组从左到右从上到下递增,那么当最右上角的数字大于目标数字就可以去掉该列,当最右边的数字小于目标数字的时候就去掉该行,
如此遍历查找(画个图体会一下)

 public boolean Find(int target, int [][] array) {
        if(array.length==0) return false;
        int i=0;
        int j=array[0].length-1;
        while(i<array.length&&j>=0){
            //注意  这里是if  else if 不是连续的if 否则可能 out of index 
            if(array[i][j]==target)  return true;
            else if(array[i][j]>target){
                j--;
            }
            else if(array[i][j]<target){
                i++;
            }
        }
        return false;
    }

4.替换字符串中的空格
题目:https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
思路:可以用String类的replace()函数来做。但是这个题考察的不是。
这个题有点考察可变字符串 StringBuffer的意思
=

      public String replaceSpace(StringBuffer str) {
        /*String str1=str.toString();
        String str2=str1.replace(" ","%20");
        return str2;*/
        //这个题目的不是考库函数
        if(str==null) return null;
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' ') sb.append("%20");
            else sb.append(str.charAt(i));
        }
        return sb.toString();
    }

5. 从尾到头打印链表
题目:https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:借助栈来实现先进后出
栈:Stack 类 push()压栈 pop()出栈 peek() isEmpty()

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        Stack<Integer> stack=new Stack<Integer>();
        while(listNode!=null){
            stack.push(listNode.val);
            listNode=listNode.next;
        }
        while(!stack.isEmpty()){
            list.add(stack.pop());
        }
        return list;
    }
}

6. 由前序和中序遍历重建二叉树
题目:https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&tqId=11157&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解法:由先根遍历序列和中根遍历序列构建二叉树
java数据结构书p168
思路:1.取先根遍历序列中的第一个结点作为根结点
2.在中根遍历序列中寻找根结点,确定根结点在中根遍历序列中的位置,假设为i。
3.递归构造左右子树

public static int[] copyOfRange(int[] original,
int from,
int to)
这个方法将指定数组的指定范围复制到一个新数组。
from - 要复制的范围的初始索引(包括)
to - 要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
ArrayIndexOutOfBoundsException - 如果 from < 0 或 from > original.length()
IllegalArgumentException - 如果 from > to

 //返回的是树的根结点
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if((pre.length!=in.length)||(pre==null)||(in==null))
                  return null;
        //这个判断必不可少,否则会报java.lang.ArrayIndexOutOfBoundsException: 0
        //相当于递归终止条件
        if (pre.length == 0 || in.length == 0) {        
            return null;    }
        //获取根节点的值
        int v=pre[0];
        //构建根结点
        TreeNode root=new TreeNode(v);
        int i=0;
        //从中序序列里找到根节点的位置
        for(i=0;i<in.length;i++){
            if(in[i]==v) {
               break;
            }
        }
        //递归构建左右子树
        /*
        public static int[] copyOfRange(int[] original,
                                int from,
                                int to)
         这个方法将指定数组的指定范围复制到一个新数组。
         from - 要复制的范围的初始索引(包括)
         to - 要复制的范围的最后索引(不包括)。(此索引可以位于数组范围之外)。
         ArrayIndexOutOfBoundsException - 如果 from < 0 或 from > original.length()
         IllegalArgumentException - 如果 from > to
        */
         root.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1), Arrays.copyOfRange(in,0,i));
         root.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
        return root;
    }

7. 用两个栈实现队列
题目:https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:将栈1的元素出栈后压入栈2中。先进后出再先进后出一下成了先进先出。
1.比如说来了一个序列是1 2 5 调用push()全都加进队列,则加入了栈1中。栈1中元素顺序是5 2 1。
2.这时候调用pop出队。由于栈2是空的,所以把栈1里的元素都倒出来放入栈2。
这样栈2中的元素顺序是1 2 5。然后依次把栈2中的元素出栈。

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    /*
    队列是先进先出的
    栈是先进后出
    */
    //将栈1的元素出栈后压入栈2中。先进后出再先进后出一下成了先进先出。
    //栈1用于存储元素,栈2作为缓冲
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        
        //  一定不能少了这个if判断条件  想想  ["PSH1","PSH2","PSH3","POP","POP","PSH4","POP","PSH5","POP","POP"]
        //  在把栈2出空了的情况下再出栈1    
    if(stack2.isEmpty()){
        while(!stack1.isEmpty()){
            stack2.push(stack1.pop());
        }
    }    
        
    return stack2.pop();
        
    }
}

实现队列的类并编写测试用例:

import java.util.Stack;

public  class QueueTest{

 public static void main(String args[]){
     Squeue queue=new Squeue();
     for(int i=0;i<10;i++){
         queue.push(i);
     }
    while(!queue.isEmpty()){
        System.out.println(queue.pop());
    }
 }

}

class Squeue{
    Stack<Integer> stack1=new Stack();
    Stack<Integer> stack2=new Stack();
    public void push(int node){
          stack1.push(node);
    }

    public int  pop(){
        if(stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
            return stack2.pop();
    }
    public boolean isEmpty(){
        if(stack1.isEmpty()&&stack2.isEmpty())
              return true;
        else return false;
    }


}

8.旋转数组的最小数字
题目:https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

      /*   这样做可以,但是效率不高
     for(int i=1;i<array.length;i++){
          if(array[i]<array[i-1]) return array[i];
     }
     return  array[0];
    }
找出中间打破增长趋势的点
要考虑到查找的效率。利用二分法,找到中间的数,然后和最左边的值进行比较,若大于最
左边的数,则最左边从mid开始,若小于最右边值,则最右边从mid开始。若
左中右三值相等,则取mid前后值中较小的数。

解析:
旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素

注意到实际上最小的元素就是两个子数组的分界线。本题目给出的数组一定程度上是排序的,因此我们试着用二分查找法寻找这个最小的元素。

思路:

(1)我们用两个指针left,right分别指向数组的第一个元素和最后一个元素。按照题目的旋转的规则,第一个元素应该是大于最后一个元素的(没有重复的元素)。

但是如果不是旋转,第一个元素肯定小于最后一个元素。

(2)找到数组的中间元素。

中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。

移动之后,第一个指针仍然位于前面的递增数组中。

中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。

移动之后,第二个指针仍然位于后面的递增数组中。

这样可以缩小寻找的范围。

(3)按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。

最终第一个指针将指向前面数组的最后一个元素,第二个指针指向后面数组中的第一个元素。

也就是说他们将指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。
    */
public int minNumberInRotateArray(int [] array) {
   int left=0;
   int right=array.length-1;
   int mid=-1;
    //这样做可以快速逼近那个点
   while(array[left]>=array[right]){
       if(right-left==1){
           mid=right;
           break;
       }
      mid=(right+left)/2;
    if(array[mid]>=array[left]){
          left=mid;
       }
    if(array[mid]<=array[right]){
        right=mid;
    }
   }
    return array[mid];
    }

9. 斐波那契数列的应用
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
9.1 输出斐波那契数列的第n项
题目:https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
这个题可以用递归,但是递归的效率不如动态规划高。
动态规划:

 public int Fibonacci(int n) {
  //F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
     if(n<=0)  return 0;
     //优先把n==1和n==2的情况处理掉是必要的,否则后面dp[2]=1可能会抛异常。
     if(n==1 ||n==2) return 1;
     int dp[]=new int[n+1];  //dp[i]代表数列第i项
     dp[1]=1;
     dp[2]=1;
     for(int i=3;i<=n;i++){
         dp[i]=dp[i-1]+dp[i-2];
     }
      return dp[n];
    }

递归:

public int Fibonacci(int n) {    
  if(n==0||n==1) 
      return n;
   else{
     return Fibonacci(n-1)+Fibonacci(n-2);
   }     
    }

9.2. 青蛙跳台阶(1或2级)
题目:https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
e.可以发现最终得出的是一个斐波那契数列:
| 1, (n=1)
f(n) = | 2, (n=2)
| f(n-1)+f(n-2) ,(n>2,n为整数)

  public int JumpFloor(int target) {
        if(target<=0)
               return 0;
        if(target==1)
            return 1;
        if(target==2)
            return 2;
            //dp[i]   跳n阶台阶共有多少种方法
        int dp[]=new int[target+1];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=target;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[target];
    }

9.3 小矩形无重叠覆盖大矩形
题目:https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
这个题就是青蛙跳台阶。一次跳一阶或者两阶,问多少种跳法。
可以横着放或竖着放,f(n)=f(n-1)+f(n-2)
f(1)=1,f(2)=2 和上面一摸一样的代码

  public int RectCover(int target) {
     if(target<=0)
               return 0;
        if(target==1)
            return 1;
        if(target==2)
            return 2;
            //dp[i]   跳n阶台阶共有多少种方法
        int dp[]=new int[target+1];
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=target;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[target];
    }

9.4青蛙跳台阶(n级)
题目:https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
因为n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
所以f(n)=f(n-1)+f(n-2)+…+f(1)+f(0)
因为f(n-1)=f(n-2)+f(n-3)+…+f(1)+f(0)
所以f(n)=2f(n-1)
得出最终结论,在n阶台阶,一次有1、2、…n阶的跳的方式时,总得跳法为:
| 1 ,(n=0 )
f(n) = | 1 ,(n=1 )
| 2
f(n-1),(n>=2)

  public int JumpFloorII(int target) {
         if(target<=0)  
             return 0;
         if(target==1)
             return 1;
        if(target==2)
            return 2;
        int dp[]=new int[target+1];
        dp[1]=1;
        for(int i=2;i<=target;i++){
             dp[i]=2*dp[i-1];
        }
        return dp[target];
    }

10. 二进制中1的个数
题目:https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&tqId=11164&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
思路:a&(a-1)的结果会将a中的一个1变成0,直到a = 0,还可以先将a&1 !=
0,然后右移1位,但不能计算负数的值.
比如n=9 二进制形式1001,n-1=8二进制形式1000
9&8=1000 再1000&0111=0。所以9中有两个1。
关于java的位运算符:
http://www.runoob.com/java/java-operators.html
&就是两个数的二进制形式按位运算。都为1,才是1,否则为0.

  public int NumberOf1(int n) {
        int num=0;
        while(n!=0){
            num++;
            n=n&(n-1);
        }
        return num;
    }

11. 数值的整数次方
题目:https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路: 1.要判断底数是否为0。因为当底数为0且指数<0时,

  • 会出现对0求倒数的情况;
  • 2.判断底数是否等于0
  • 由于base为double型,不能直接用==判断.
    可以使用BigDemical,也可以用这个代码里的自定义比较精度。
    3.要具体的讨论指数的各种情况
    为0,大于0,小于0

怎么判断两个double值是否相等:

if(Math.abs(num1-num2)<0.000001)
            return true;
小数点后6
 public double Power(double base, int exponent) {
     /*
     本题的细节:
     1.要判断底数是否为0。因为当底数为0且指数<0时,
*    会出现对0求倒数的情况;
*    2.判断底数是否等于0
*    由于base为double型,不能直接用==判断.
   可以使用BigDemical,也可以用这个代码里的自定义比较精度。
     3.要具体的讨论指数的各种情况
     为0,大于0,小于0
     */
       double res=0.0;
     //当底数为0
        if(equal(base,0)){
            return 0;
        }
     //讨论指数的三种情况
        if(exponent==0){
           return 1.0;
        }
        else if(exponent>0){
            res=multiply(base, exponent);
        }
       else if(exponent<0){
            res=multiply(1.0/base, -exponent);
        }
        return res;
    }
    public boolean equal(double num1,double num2){
        if(num1-num2>-0.000001&& num1-num2<0.000001)
            return true;
        else
            return false;
    }

   public double multiply(double base,int exponent){
          double sum=1;
          for(int i=0;i<exponent;i++){
                sum=sum*base;
          }
             return sum;
    }

这种算法的复杂度比较高 可以尝试使用快速幂算法

import java.util.*;
public class Solution {

/**
 * 1.全面考察指数的正负、底数是否为零等情况。
 * 2.写出指数的二进制表达,例如13表达为二进制1101。
 * 3.举例:10^1101 = 10^0001*10^0100*10^1000。
 * 4.通过&1和>>1来逐位读取1101,为1时将该位代表的乘数累乘到最终结果。
 */
public double Power(double base, int exponent) {
       double res=0.0;
     //当底数为0
        if(equal(base,0)){
            return 0;
        }
     //讨论指数的三种情况
        if(exponent==0){
           return 1.0;
        }
        else if(exponent>0){
            res=multiply(base, exponent);
        }
       else if(exponent<0){
            res=multiply(1.0/base, -exponent);
        }
        return res;
    }
    public boolean equal(double num1,double num2){
        if(Math.abs(num1-num2)<0.000001)
            return true;
        else
            return false;
    }

   public double multiply(double base,int exponent){
       double result =1.0;
       while(exponent!=0){
           //注意  位运算符优先级低于比较运算符  这里要加括号
           if((exponent&1)==1){
               result =result*base;
           }
           base= base * base;
           exponent= exponent >>1;
       }
        return result;
    }
}

有时间可以看看 关于快速幂算法的思路:
https://blog.csdn.net/hkdgjqr/article/details/5381028

14. 将数组中的奇数放在偶数前
题目:https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
本题可以有两种思路来做。
一种是另外定义一个数组,把当前数组中的数据奇数,偶数依次复制进去即可。
**另一种是用插入排序的思想。**这里分成两个序列的要求可能会想到快排,
但是快排是不稳定的排序算法,无法保持数据相对位置不变。
稳定的排序算法有直接插入排序,冒泡排序,归并排序。。
这里用直接插入排序的思想。java数据结构课本p243
1.遍历数组中的数据,如果array[i]是奇数,将array[i]存储在临时变量temp中
2.如果arrayj的值是偶数,就把它们后移一位。直到array[j]的位置上是奇数或者j=0
3.把 temp插入到第j+1个位置上
另外定义数组:

   public void reOrderArray(int [] array) {
          int [] arr=new int[array.length];
          int index=0;
         for(int i=0;i<array.length;i++){
             if(array[i]%2!=0){
                 arr[index]=array[i];
                 index++;
             }
         }
        for(int i=0;i<array.length;i++){
            if(array[i]%2==0){
                arr[index]=array[i];
                index++;
            }
        }
        for(int i=0;i<arr.length;i++){
            array[i]=arr[i];
        }
    }

直接插入排序:

public void reOrderArray(int[] array) {
        if (array == null) return;
        int i,j;
        for ( i = 1; i < array.length; i++) {
            int temp = array[i];
            //如果是奇数
           if(array[i]%2!=0){
               for(j=i-1;j>=0&&array[j]%2==0;j--){
                   array[j+1]=array[j];
               }
               //这时候找到恰当的插入位置了
               array[j+1]=temp;
           }
        }
    }

15. 求链表中倒数第K个节点
题目:https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:定义一快一慢两个指针,快指针走K步,然后慢指针开始走,快指针 到尾时,慢指针就找到了倒数第K个节点。

import java.util.*;
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
    //第一步这个条件不可少
  if(head==null||k<=0)
      return null;
     //定义一快一慢两指针  
   ListNode fast=head;    
   ListNode slow=head;    
   //快的先走k-1步    
   while(k>1){
       if(fast.next!=null){
           fast=fast.next;
       }
       //列表里本来就没有K个结点
       else{
           return null;
       }
       k--;
   }    
        //快指针移动到链表末尾
   while(fast.next!=null){
       slow=slow.next;
       fast=fast.next;
   }    
    //慢指针就指向所求结点
        return slow;
     
    }
}

16. 输出反转后的链表
题目:https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

  public ListNode ReverseList(ListNode head) {
         // 1-> 2 -> 3 -> 4 -> 5
         // 1 <- 2  3 - >4  3存到next
          if(head==null)  return  null;
         //head代表当前结点 
         //pre代表当前结点的前一个  next代表后一个
         /*
         需要pre和next的目的是让当前节点从pre->head->next1->next2变成pre<-head next1->next2
         即pre让节点可以反转所指方向,但反转之后如果不用next节点保存next1节点的话,此单链表就此断开了
         所以需要用到pre和next两个节点
         1->2->3->4->5
         1<-2<-3 4->5 
         */
          ListNode   pre= null;
          ListNode   next= null;
          while(head!=null){
              //先把next存下来防止断裂
              next =head.next;
              //反转指针
              head.next = pre;
              pre= head;
              head = next;
          }
          
          return pre;
    }

17. 合并两个有序链表
题目:https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:递归

public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null){
            return list2;
        }
        if(list2==null){
            return list1;
        }
        //这个结点是合成链表的头结点
        ListNode newhead=null;
        if(list1.val<=list2.val){
            newhead=list1;
            newhead.next=Merge(list1.next,list2);
        }
        else{
            newhead=list2;
            newhead.next=Merge(list1,list2.next);
        }
          return newhead;
    }

18. 判断二叉树A中是否包含子树B
题目:https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&tqId=11170&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

   public boolean HasSubtree(TreeNode root1,TreeNode root2) {
     boolean result=false;
        /*
  若根节点相等,则从当前结点起,判断一棵树是否包含另一颗树,若根节点不相等,则利用递归分别在左右子树中查找。
          */
     if(root1!=null&&root2!=null){
         //如果当前根结点和root2相等
         if(root1.val==root2.val){
             //就以当前根结点为起点判断是否包含tree2
             result=HasTree(root1,root2);       
         }
         //如果找不到,去左子树找
      if(!result){
          result=HasSubtree(root1.left,root2);
      }
         //再找不到,去右子树找
       if(!result){
          result=HasSubtree(root1.right,root2); 
      }     
     }
         return result;
    }
    //判断从当前结点起,一棵树是否包含另一颗树
    public boolean HasTree(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 HasTree(node1.left,node2.left)&&HasTree(node1.right,node2.right);
    }

简化版的写法:

  public boolean HasSubtree(TreeNode root1,TreeNode root2) {
            if(root1==null||root2==null)
                  return false;
            //以当前根结点为起点判断是否包含tree2
            if(Hastree(root1, root2))  return true;
            //找不到,去左右子树中找
            else  return  HasSubtree(root1.left,root2)|| HasSubtree(root1.right,root2);
    }
     //判断从当前结点起,一棵树是否包含另一颗树
    public boolean Hastree(TreeNode root1,TreeNode root2){
        //如果Tree2已经遍历完了都能对应的上,返回true
        if(root2==null)  return true;
        //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
        if(root1==null)  return false;
        //如果其中有一个点没有对应上,返回false
        if(root1.val!=root2.val) return false;
        //如果根节点对应的上,那么看左右子节点是否匹配
        return Hastree(root1.left,root2.left)&&Hastree(root1.right,root2.right) ;
}

19. 二叉树的镜像
题目:https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
递归 如果root!=null 则交换左右子树
注意这个递归终止条件 只需判断 root是否为null
无需判断root.left和root.right是否为null

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

20. 顺时针打印矩阵
题目:https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a?tpId=13&tqId=11172&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

public ArrayList<Integer> printMatrix(int [][] matrix) {
       ArrayList<Integer> list=new ArrayList<Integer>();
       if(matrix.length==0)
                 return list;
        //当前开始的索引 行/列相同
        int start=0;
        //当前结束的列索引
        int end1=matrix[0].length-1;
        //当前结束的行索引
        int end2=matrix.length-1;
        while(start<=end1&&start<=end2){
        //顺时针扫一圈    
        cprint(matrix,list,start,end1,end2);
        start++;
        end1--;
        end2--;    
        }
        return list;
    }
    //扫一圈 
public void cprint(int [][] matrix,ArrayList<Integer> list,int start,int end1,int end2){
        //从左向右走
        for(int i=start;i<=end1;i++){
            list.add(matrix[start][i]);
        }
         //从上向下走.start+1是因为刚才扫过了
        for(int i=start+1;i<=end2;i++){
            list.add(matrix[i][end1]);
        }
    //往回扫的时候注意,要进行判断,防止跟从左往右的时候重复了
        if(start<end2){
           //从右向左走
        for(int i=end1-1;i>=start;i--){
            list.add(matrix[end2][i]);
        }
        }
    //往回扫的时候注意,要进行判断,防止跟从上往下的时候重复了
       if(start<end1){
           //从下向上走
        for(int i=end2-1;i>start;i--){
            list.add(matrix[i][start]);
        }     
            
        }
    }

21. 包含min函数的栈
题目:https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:定义两个栈,一个存所有入栈的值。另一个存最小值。
思路:用一个栈data保存数据,用另外一个栈min保存依次入栈最小的数
比如,data中依次入栈,5, 4, 3, 8, 10, 11, 12, 1
则min依次入栈,5, 4, 3,no,no, no, no, 1
no代表此次不入栈每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不入栈。

     /*
     定义两个栈,一个存所有入栈的值。另一个存最小值。
思路:用一个栈data保存数据,用另外一个栈min保存依次入栈最小的数
比如,data中依次入栈,5,  4,  3, 8, 10, 11, 12, 1
       则min依次入栈,5,  4,  3,no,no, no, no, 1
 用例:
["PSH3","MIN","PSH4","MIN","PSH2","MIN","PSH3","MIN","POP","MIN","POP","MIN","POP","MIN","PSH0","MIN"]
对应输出应该为:
3,3,2,2,2,3,3,0
no代表此次不入栈每次入栈的时候,如果入栈的元素比min中的栈顶元素小或等于则入栈,否则不入栈。
     */
//栈1用来存放所有元素
    Stack<Integer> s1=new Stack<Integer>();
    //栈2的栈顶永远是最小值
    Stack<Integer> s2=new Stack<Integer>();
    public void push(int node) {
        s1.push(node);
         if(s2.isEmpty()|| node<=s2.peek()){
             s2.push(node);
         }
    }
  //注意pop()的写法  如果栈2的栈顶元素等于出栈元素,才出栈
    public void pop() {
         int temp=s1.pop();
         if(s2.peek()==temp){
             s2.pop();
         }
    }

    public int top() {
        return s1.peek();
    }

    public int min() {
        return s2.peek();
    }     

22. 判断一个栈是否是另一个栈的弹出序列
题目:https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

    public boolean IsPopOrder(int [] pushA,int [] popA) {
      if(pushA==null || popA==null)
            return false;
        Stack<Integer> s=new <Integer>Stack();
        //一定要注意这个index是整体维护的
        int index=0;
        for(int i=0;i<pushA.length;i++){
            s.push(pushA[i]);
            //注意这里的两个判断条件别反了
            while(!s.isEmpty()&&popA[index]==s.peek()){
                s.pop();
                index++;
            }
        }
        return s.isEmpty();
    }

23.层序遍历二叉树
题目:https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:层次遍历
1.层次遍历操作用一个队列作为辅助数据结构。这里用Queue的实现子类LinkedList。
2.首先将根结点入队,
3.然后循环,在队列非空的条件下,每次取出队首元素访问。每处理一个结点,都先访问该结点,其孩子结点从左到右依次入队。
4.直到队列为空

/*
层次遍历
1.层次遍历操作用一个队列作为辅助数据结构。这里用Queue的实现子类LinkedList。
2.首先将根结点入队,
3.然后循环,在队列非空的条件下,每次取出队首元素访问。每处理一个结点,都先访问该结点,其孩子结点从左到右依次入队。
4.直到队列为空
JAVA中用队列  一般用Queue。
Queue是一个接口,它的实现类可以用LinkedList  实现了Queue接口.
offer()入队  poll()出队
首元素可以用peek()   判断队列里有无元素 用isEmpty()
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        if(root==null) return list;
         Queue<TreeNode> queue=new LinkedList<TreeNode>();  
        queue.offer(root);
      //  list.add(root.val);
        while(!queue.isEmpty()){
            TreeNode temp=queue.poll();
            list.add(temp.val);
            if(temp.left!=null)
                queue.offer(temp.left);
            if(temp.right!=null)
                queue.offer(temp.right);
        }
       return list;
         
    }

24. 后序遍历二叉搜索树
题目:https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。
1、确定root;
2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
3、遍历右子树,若发现有小于root的值,则直接返回false;
4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。
二叉搜索树:课本p292
二叉搜索树=二叉排序树=二叉查找树
二叉搜索树或者是一棵空树,或者是一棵具有如下性质的二叉树:
1.若左子树不空,则左子树上所有的结点的值均小于根结点的值。
2.若右子树不空,则右子树上所有的结点的值均大于根结点的值。
3.其左右子树也是二叉搜索树

   public boolean VerifySquenceOfBST(int [] sequence) {
    /*   
    已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。
    后序遍历: 左右根
1、确定root;
2、遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
3、遍历右子树,若发现有小于root的值,则直接返回false;
4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。     
      */ 
   if(sequence==null||sequence.length==0){
          return false;
   }
         return BST(sequence);
    }
   public boolean BST(int [] array){
       //递归终止条件
        if(array.length==0){
          return true;
           }
       //1.确定root
    int root=array[array.length-1];  
       //2.遍历序列找到第一个大于root的位置,这是左子树与右子树的分割处
    int rstart=0;   
       //这里有两种可能,一种是找到了使得array[rstart]>root的rstart。
       //另一种是没找到这样的rstart,右子树为空
    for(rstart=0;rstart<array.length-1;rstart++){
        if(array[rstart]>root)
            break;
    } 
       //3、遍历右子树,若发现有小于root的值,则直接返回false;
      for(int i=rstart;i<array.length-1;i++){
        if(array[i]<root)
            return false;
    }   
       //4、分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。
       return BST(Arrays.copyOfRange(array,0,rstart))&&BST(Arrays.copyOfRange(array,rstart,array.length-1));    
   }

25. 二叉树中和为某值的路径
题目:https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
:输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入
整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的
结点形成一条路径。
思路:先保存根节点,然后分别递归在左右子树中找目标值,若找到即到达
叶子节点,打印路径中的值

 ArrayList<ArrayList<Integer>> resultList=new ArrayList<ArrayList<Integer>>();
    ArrayList<Integer> list=new ArrayList<Integer>();
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
            if(root==null) return resultList;
            list.add(root.val);
            target=target-root.val;
           //如果到了叶子结点,且数值达到target,说明找到一条路径了
            if(target==0&&root.left==null&&root.right==null){
                //不重新new的话从始至终resultList中所有引用都指向了同一个list
                //直接添加的话,后面对list的remove操作就会影响resultList里面的元素了
                      resultList.add(new ArrayList(list));
            }
            //还没找到  去左右子树中遍历
        else{
            FindPath(root.left,target);
            FindPath(root.right,target);
        }
        //无论找到没找到都回退,去掉list中最后一个元素 
        //每返回上一层一次就要回退一个结点
            list.remove(list.size()-1);
            return resultList;
    }

26. 复杂链表的复制
题目:https://www.nowcoder.com/practice/f836b2c43afc4b35ad6adc41ec941dba?tpId=13&tqId=11178&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead==null)
              return null;
        RandomListNode head=new RandomListNode(pHead.label);
        RandomListNode temp=head;

        while(pHead.next!=null){
            temp.next=new RandomListNode(pHead.next.label);
            if(pHead.random!=null){
               temp.random=new RandomListNode(pHead.random.label);   
            }
            pHead=pHead.next;
            temp=temp.next;
        }
        return head;

    }

27.二叉搜索树转换为双向链表
https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

1.将左子树构造成双链表,并返回链表头节点。
2.定位至左子树双链表最后一个节点。
3.如果左子树链表不为空的话,将当前root追加到左子树链表。
4.将右子树构造成双链表,并返回链表头节点。
5.如果右子树链表不为空的话,将该链表追加到root节点之后。
6.根据左子树链表是否为空确定返回的节点。
*/
public class Solution {
    //记作左子树的末尾结点
   TreeNode leftLast = null;
   //返回双向链表的首结点
   //双向链表 left指向前一个结点 right 指向后一个结点
    public TreeNode Convert(TreeNode root) {
        if(root==null)
            return null;
        // 1.将左子树构造成双链表,并返回链表头节点
        TreeNode left = Convert(root.left);
        // 3.如果左子树链表不为空的话,将当前root追加到左子树链表
        if(left!=null){
            leftLast.right = root;
            root.left = leftLast;
        }
        leftLast = root;// 把根节点追加到左链表的末尾
        // 4.将右子树构造成双链表,并返回链表头节点
        TreeNode right = Convert(root.right);
        // 5.如果右子树链表不为空的话,将该链表追加到root节点之后
        if(right!=null){
            right.left = root;
            root.right = right;
        }
        return left!=null?left:root;       
    }

}

28. 打印字符串中所有字符的排列
题目:https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
这是一道求全排列的问题。
关于全排列问题的解决思路:http://www.cnblogs.com/cxjchen/p/3932949.html

   public ArrayList<String> Permutation(String str) {
        /*
采用递归的方式求解。每次先选定一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来。
至此,一趟全排列完成。第二趟,选定下一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来。.....
设R={r1,r2,...rn}是要进行排列的n个元素.Ri=R-{ri}.集合X中元素的全排列记为Perm(X).(ri)。
Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀ri得到的排列
    R的全排列可归纳定义如下:
        当n=1时,Perm(R)=(r),其中r是集合R中唯一的元素;
        当r>1时,Perm(R)由(r1)Perm(r1),(r2)Perm(r2).....(rn)Perm(rn)构成.
        */   
    ArrayList<String> list=new ArrayList<String>();
    if(str==null||str.length()==0)
        return list;
     char []chars=str.toCharArray();
        //这个TreeSet的作用是去重加排序,主要是去重。因为有重复的字符。
     TreeSet<String> temp=new TreeSet<String>();
     Permu(chars,0,temp); 
   list.addAll(temp);
   return list;
    }
  public void Permu(char[]chars,int index,TreeSet<String> temp){
              if(index == chars.length-1) {       
                   temp.add(String.valueOf(chars)) ;   
               }
              else{
                  //遍历r0,r1,r2...,rn
              for(int i=index;i<=chars.length-1;i++){
                    //ri
                    swap(chars,index,i);
                  //Perm(ri)
                    Permu(chars,index+1,temp);
                  //回退
                    swap(chars,index,i);
              } 
              }  
  } 
    //交换字符数组中两元素
   public void swap(char[]chars,int a,int b){
       char temp=chars[a];
       chars[a]=chars[b];
       chars[b]=temp;
   }   
     

采用递归的方式求解。每次先选定一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来。
至此,一趟全排列完成。第二趟,选定下一个字符,然后进行“若干次”交换,求出在选定这个字符的条件下,所有的全排列,并把字符“复位”再交换回来。…
设R={r1,r2,…rn}是要进行排列的n个元素.Ri=R-{ri}.集合X中元素的全排列记为Perm(X).(ri)。
Perm(X)表示在全排列Perm(X)的每一个排列前加上前缀ri得到的排列
R的全排列可归纳定义如下:
当n=1时,Perm®=®,其中r是集合R中唯一的元素;
当r>1时,Perm®由(r1)Perm(r1),(r2)Perm(r2)…(rn)Perm(rn)构成.

public class Permutation {
    ArrayList<String> result=new ArrayList<String>();
    TreeSet<String> set=new TreeSet<String>();
    public ArrayList<String> Permutation(String str) {
        if(str.length()==0)
            return  result;
        char ch[]=str.toCharArray();
        dfs(str,0,ch);
        return result;
    }

    void dfs(String str,int index,char ch[]){
     if(index==str.length()-1){
         set.add(new String(ch));
     }
    else{
        for(int i=index;i<ch.length;i++){
            swap(index,i,ch);
            dfs(str,index+1,ch);
            swap(index,i,ch);
        }
     }
    }
    void swap(int index,int i,char c[]){
        char temp=c[index];
        c[index]=c[i];
        c[i]=temp;
    }
}

补充:另一道关于全排列的问题
https://mp.csdn.net/mdeditor/88413149#

29. 数组中出现次数超过一半的数字(时间效率的优化)
题目:https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=2&rp=2&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
这道题写嵌套循环也能过,但是时间复杂度较高。现提出两种解决思路:
思路1:先对数组排序,数组排序后,如果符合条件的数存在,则一定是数组中间那个数。
思路2:如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。
遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。
遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
思路1代码:
public int MoreThanHalfNum_Solution(int [] array) { Arrays.sort(array); int tmp=array[array.length/2]; int count=0; for(int i=0;i<array.length;i++){ if(array[i]==tmp){ count++; } } if(count>array.length/2) return tmp; else return 0; }
思路2代码:

import java.util.*;
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
        /*
        这道题写嵌套循环也能过,但是时间复杂度较高。现提出两种解决思路:
        思路1:先对数组排序,数组排序后,如果符合条件的数存在,则一定是数组中间那个数。
        思路2:如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。
遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。
遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。
        */
       int num=array[0];
       int count=1;
        //这一趟循环是为了找出可能的那个数字,存在num里
    for(int i=1;i<array.length;i++){
         if(count==0){
             num=array[i];
             count=1;
         }
         else{
            if(array[i]==num){
                count++;
            }
            if(array[i]!=num){
                count--;
            }
         }
        }
        int ncount=0;
        //验证一下找出来的这个数字是否符合超过一半。
       for(int i=0;i<array.length;i++){
           if(array[i]==num){
               ncount++;
           }
       }
        if(ncount>array.length/2){
            return num;
        }
        else return 0;
    }
}

30.最小的k个数
题目:https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
可以考虑使用快排,冒泡排序,推排序等等

快速排序:
快排这里无需把整个数组的顺序全排出来,只要index=k-1即可。

import java.util.*;
public class Solution {
   public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k)  {
        ArrayList<Integer> list = new ArrayList<>();
       //这里快速排序必须加上k<=0   否则下面会构成一个死循环,造成超时
        if (input.length < k || input.length == 0 ||k<=0)
            return list;
        int start = 0;
        int end = input.length - 1;
        int index=partition(input,start,end);
        while(index!=k-1){
             if(index>k-1){
                 end=index-1;
                 index=partition(input,start,end);
             }
            if(index<k-1){
                start=index+1;
                index=partition(input,start,end);
            }
        }
        for(int i=0;i<k;i++){
            list.add(input[i]);
        }
        return list;
    }

    static int partition(int[] arr, int i, int j) {
        int pivot = arr[i];   //把第一个记录作为支点记录
        while (i < j) {       //从表的两端交替往中间扫描
            //从右往左找  直到找到比支点小的记录
            while (i < j && pivot <= arr[j]) {
                j--;
            }
            //2.将右侧找到小于基准数的值加入到左边的(坑)位置, 左指针想中间移动一个位置
            if (i < j) {
                arr[i] = arr[j];
                i++;
            }
            //3.从左向右移动i,找到第一个大于等于基准值的值 arr[i]
            while (i < j && pivot >= arr[i]) {
                i++;
            }
            //4.将左侧找到的打印等于基准值的值加入到右边的坑中,右指针向中间移动一个位置 j--
            if (i < j) {
                arr[j] = arr[i];
                j--;
            }
        }
        //使用基准值填坑,这就是基准值的最终位置
        arr[i] = pivot;   //支点记录到位
        return i;         //返回支点位置索引
    }
}

也可以采用冒泡排序,时间复杂度稍大些。冒泡排序是稳定的排序算法。

在这里插入代码片   public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k)  {
        ArrayList<Integer> list = new ArrayList<>();
       //这里快速排序必须加上k<=0   否则下面会构成一个死循环,造成超时
        if (input.length < k || input.length == 0 ||k<=0)
            return list;
       //只要最小的k个沉在最下面就好
       //外层循环控制排序趟数
       boolean flag=true;
       for(int i=0;i<k&&flag;i++){
             flag=false;
           //每一趟排序,都把最小的记录沉在最下面
           for(int j=0;j<input.length-1;j++){
               if(input[j]<input[j+1]){
                   int tmp=input[j+1];
                   input[j+1]=input[j];
                   input[j]=tmp;
                   flag=true;
               }  
           }
       } 
         for(int i=input.length-1;i>=input.length-k;i--){
             list.add(input[i]);
         }
              return list;
    } 

注:Arrays.sort()和Collections.sort()的底层是归并排序。
31.连续子数组的最大和
题目:https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:使用动态规划。
dp(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
dp(i)=max(dp(i-1)+array[i],array[i]) 状态转移方程
max:所有子数组的和的最大值
max=max(max,dp(i))
参考:https://mp.csdn.net/mdeditor/88391768#

 /*
      动态规划步骤:
      1.定义状态    int dp[]=new int[array.length]
     dp(i):以array[i]为末尾元素的连续子数组的和的最大值
      2.定义状态转移方程    dp(i)=max(dp(i-1)+array[i],array[i])
      3.初始化状态
     */
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length==0)  return 0;
       //1
        int dp[]=new int[array.length];
        //2
        for(int i=0;i<array.length;i++){
            dp[i]=array[i];
        }
        int max=dp[0];
        //3
        for(int i=1;i<array.length;i++){
            dp[i]=Math.max(dp[i-1]+array[i],dp[i]);
            if(dp[i]>max)   max=dp[i];
        }
        return max;
    }

32.1到n中1出现的次数
题目:https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

    /*
    主要思路:设定整数点(如1、10、100等等)作为位置点i(对应n的各位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析
    根据设定的整数位置,对n进行分割,分为两部分,高位n/i,低位n%i
当i表示百位,且百位对应的数>=2,如n=31456,i=100,则a=314,b=56,此时百位为1的次数有a/10+1=32(最高两位0~31),每一次都包含100个连续的点,即共有(a%10+1)*100个点的百位为1
当i表示百位,且百位对应的数为1,如n=31156,i=100,则a=311,b=56,此时百位对应的就是1,则共有a%10(最高两位0-30)次是包含100个连续点,当最高两位为31(即a=311),本次只对应局部点00~56,共b+1次,所有点加起来共有(a%10*100)+(b+1),这些点百位对应为1
当i表示百位,且百位对应的数为0,如n=31056,i=100,则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30)
综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有100个点,还有当百位为1(a%10==1),需要增加局部点b+1
之所以补8,是因为当百位为0,则a/10==(a+8)/10,当百位>=2,补8会产生进位位,效果等同于(a/10+1)
    */
    public int NumberOf1Between1AndN_Solution(int n) {
     int count=0;
     //i表示当前分析的是哪个数位
    for(int i = 1; i <= n; i *= 10){        
        int a = n / i; 
         int b = n % i;         
        count += (a+8) / 10 * i;        
        if(a % 10 == 1){            
            count += b + 1;        
        }    
    }    
        return count; 
    }

33. 把数组中的数排成一个最小的数
题目:https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。

import java.util.*;
 
public class Solution {
public String PrintMinNumber(int [] numbers) {
        /*
        先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。
        关键就是制定排序规则。
        注意  对于Arrays.sort()  ‘3’ 会排在‘32’ 前面   会排在'321'前面
        */
        if(numbers==null)  return "";
          String []strs=new String[numbers.length];
          for(int i=0;i<numbers.length;i++){
              strs[i]=numbers[i]+"";
           }
        Arrays.sort(strs,new Comparator<String>(){
            public int compare(String str1,String str2){
            String a=str1+str2;
            String b=str2+str1;
            return a.compareTo(b);
            }
        });
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<strs.length;i++){
              sb.append(strs[i]);
        }
        return sb.toString();
    }
}

34.求第N个丑数
题目:https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
丑数

  • 把只包含素因子2、3和5的数称作丑数(Ugly Number)。
  • 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
  • 丑数规律是前面的数*2||*3||*5都会得到一个丑数,为了避免重复 我们就想了一个办法,取当前最小的丑数然后加入丑数数组。
    */
    //要注意,后面的丑数是有前一个丑数乘以2,3,5中的一个得来。因此可以用动态规划去解
    //状态转移方程nums[i]=Math.min(nums[multiply2]*2,Math.min(nums[multiply3]*3,nums[multiply5]*5));
 public int GetUglyNumber_Solution(int index) {
        /*
        1.状态   int [] dp=new int [index]    dp[i] 表示第i+1个丑数
        2.状态转移方程
        3.初始化
         */
        if(index<=0)
            return 0;
        //1
       int [] dp=new int [index];   //dp[i]  第i+1个丑数
        //2
       for(int i=0;i<index;i++){
           dp[i]=1;
       }
       int index2=0;
       int index3=0;
       int index5=0;
       //3
       for(int i=1;i<index;i++){
         int min=Math.min(dp[index2]*2,Math.min(dp[index3]*3,dp[index5]*5));
           dp[i]=min;
         if(min==dp[index2]*2) index2++;
         if(min==dp[index3]*3) index3++;
         if(min==dp[index5]*5) index5++;
       }
            return dp[index-1];
    }

题解:
https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b

35. 第一个出现一次的字符
题目:https://www.nowcoder.com/practice/1c82e8cf713b4bbeb2a5b31cf5b0417c?tpId=13&tqId=11187&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
这个题有两种思路:
1.剑指offer上的(这种比较好)。看到一个字符串里每个字符出现多少次这样,可以想到
用一个Map来存储每个字符及其出现次数。用Linked是因为要维持输入顺序。

  public int FirstNotRepeatingChar(String str) {
        //用一个Map来存储每个字符及其出现次数。用Linked是因为要维持输入顺序
       LinkedHashMap<Character,Integer> map=new LinkedHashMap<Character,Integer>();
       for(int i=0;i<str.length();i++){
           if(map.containsKey(str.charAt(i))){
               map.put(str.charAt(i),map.get(str.charAt(i))+1);
           }
         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;

    }

另一种思路:
当indexOf==lastIndexOf
这种必须注意要存储已经被遍历过的字符

在这里插入代码片 public int FirstNotRepeatingChar(String str) {
    if(str==null) return -1;   
        //这个用于存放已经被访问过的字符。
        //否则比如google,访问到第二个o的时候,符合str.IndexOf(c)==str.lastIndexOf(c)
     StringBuffer sb=new StringBuffer();   
    for(int i=0;i<str.length();i++){
        char c=str.charAt(i);
        if(sb.toString().contains(c+"")) continue;
        int j=str.lastIndexOf(c);
        if(i==j) return i;
        sb.append(c);
        //google
    }    
     return -1;    
    }

36. 数组中逆序对的个数
题目:https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析**:本质是归并排序思想的改进,在比较时加入全局变量count进行记录逆序对的个数,若array[i]>array[j] ,则count值为mid-start+1**
归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余。
归并排序的核心:归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。
归并排序算法的讲解及java示例代码:
https://blog.csdn.net/qq_36442947/article/details/81612870

题解:

public class Solution {
 int count=0;
    public int InversePairs(int [] array) {
        /*
 本质是归并排序思想的改进,在比较时加入全局变量count进行记录逆序对的个数,
  若array[i]>array[j] ,则count值为mid-start+1
      
 归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余。
归并排序的核心:归并排序将待排序的元素序列分成两个长度相等的子序列,为每一个子序列排序,
然后再将他们合并成一个子序列。合并两个子序列的过程也就是两路归并。
        */
   if(array==null)
       return 0;
     mergeSort(array,0,array.length-1);      
     return count; 
    }
    //array是待排序数组  
public void mergeSort(int []array,int start,int end){
    int mid=(start+end)/2;
   if(start<end){
       //让前后两个子序列分别有序
        mergeSort(array,start,mid);
        mergeSort(array,mid+1,end);
       //进行两有序序列的归并
        merge(array,start,mid,end);
    }   
}
    //进行两有序序列的归并,把相邻的两个有序表归并成一个有序表
public void merge(int array[],int start,int mid,int end){
   //存放合并后的序列
    int []order=new int[end-start+1];
    int i=start; 
    int j=mid+1;
    int k=0;
    int s=start;
    //将两个相邻子序列归并到order中
    while(i<=mid&&j<=end){
        //较小值复制到order中
        if(array[i]<=array[j]){
            order[k++]=array[i++];
        }
        /*
        前面的数组值array[i]大于后面数组值array[j]时;
        则前面数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
        因为两数组都是有序的(从小到大)
        */
        else {
            order[k++]=array[j++];
            count+=mid-i+1;
            count %= 1000000007; 
        }
    }
    //将前一个子序列剩余元素复制到order
    while(i<=mid){
        order[k++]=array[i++];
    }
     //将后一个子序列剩余元素复制到order
    while(j<=end){
        order[k++]=array[j++];
    }
    //这里一定要用排序后的数组覆盖原数组。否则原数组还是无序的。
   for(int o:order){
        array[s++]=o;
    }
}     
}

37. 两个链表的第一个公共节点
题目:https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

    /*
    注意  这里讨论的是单链表   公共结点的意思是两个链表相遇之后后面都是一样的  因为同一个结点只能有一个next  这俩链表像一个Y字.  
    1.如果两链表长度相等
    1.1如果它们有公共的结点,那么第一遍遍历就能找到公共结点。
    1.2如果没有公共结点,两链表同时到达链表尾部。p1=p2=null,从而跳出循环返回null。
   
    2.如果两链表长度不等
 假定 List1长度:  a+n  List2 长度:b+n, 且 a<b  n是公共结点后面的长度
        那么 p1 会先到链表尾部, 这时p2 走到 a+n位置,将p1换成List2头部
        接着p2 再走b+n-(n+a) =b-a 步到链表尾部, 将p2 换成 List1头部,
        这时p1走到List2的b-a位置,由于两列表长度差是b-a,所以相当于两链表同步了。
        那么接下来走下去在到达两列表末尾之前一定能找出公共节点,
        或者p1和p2一起到达列表尾部,二者没有公共节点,退出循环。 同理a>=b.
        时间复杂度O(n+a+b)
 也就是:       
长度相同有公共结点,第一次就遍历到;没有公共结点,走到尾部NULL相遇,返回NULL
长度不同有公共结点,第一遍追平差值,第二遍一起到公共结点;没有公共,一起到结尾NULL。 
    */
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
         ListNode p1=pHead1;
         ListNode p2=pHead2;
        while(p1!=p2){
            p1=(p1!=null?p1.next:pHead2);
            p2=(p2!=null?p2.next:pHead1);
        }
        return p1;
    }

38. 求某个数在有序数组中出现次数
题目:https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:由于是有序数组,所以这个数出现的若干次肯定是挨着的。可以使用二分查找找到这个数某一次出现的下标,然后顺着这个下标向前和后找即可。

public class GetNumberOfK {
    public int GetNumberOfK(int [] array , int k) {
          if(array.length==0)   return 0;
          int low =0;
          int high = array.length-1;
          while(low<=high){
              int mid=(low+high)/2;
              if(array[mid]==k){
                  return GetNumber(array,k,mid);
              }
              if(k<array[mid]){
                  high=mid-1;
              }
              if(k>array[mid]){
                  low=mid+1;
              }
          }
              return 0;
    }
    public int GetNumber(int [] array , int k,int index){
        int sum=1;
        for(int i=index-1;i>=0;i--){
            if(array[i]!=k){
                break;
            }
              sum++;
        }
        for(int i=index+1;i<array.length;i++){
            if(array[i]!=k){
                break;
            }
            sum++;
        }
           return sum;
    }
}

39. 1二叉树的深度
题目:https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b?tpId=13&tqId=11191&tPage=2&rp=2&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路: 1.若二叉树为空,则返回0值。
1.1求左子树的深度
1.2求右子树的深度
1.3求左,右子树深度的最大值加1,并返回其值

    public int TreeDepth(TreeNode root) {
        /*
           1.若二叉树为空,则返回0值。
            1.1求左子树的深度
            1.2求右子树的深度
            1.3求左,右子树深度的最大值加1,并返回其值
        */
     if(root!=null){
     int left=TreeDepth(root.left);
     int right=TreeDepth(root.right);   
     return Math.max(left,right)+1;
     }
        return 0;
    }

39.2 判断某二叉树是否是平衡二叉树
题目:https://www.nowcoder.com/practice/8b3b95850edb4115918ecebdf1b4d222?tpId=13&tqId=11192&tPage=2&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:
平衡二叉树:或是一棵空树,或是一棵具有如下性质的二叉树:
1.左右子树深度之差的绝对值不超过1
2.其左右子树也是平衡二叉树

public class Solution {
    /*
    1.左右子树深度之差的绝对值不超过1
    2.其左右子树也是平衡二叉树
    2.是二叉搜索树
    */
       public boolean IsBalanced_Solution(TreeNode root) {
        if(root==null) return true;
        int leftdepth=depth(root.left);
        int rightdepth=depth(root.right);
        if(Math.abs(leftdepth-rightdepth)>1) return false;
        return IsBalanced_Solution(root.left)&&IsBalanced_Solution(root.right);
    }
 
    public int depth(TreeNode root) {
      if(root==null) return 0;
       return 1+Math.max(depth(root.left),depth(root.right));
    }
 
}

40. 找出只出现一次的数字
题目:https://www.nowcoder.com/practice/e02fdb54d7524710a7d664d082bb7811?tpId=13&tqId=11193&tPage=2&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:
题目为什么要强调有一个数字出现一次,其他的出现两次?
我们想到了异或运算的性质:任何一个数字异或它自己都等于0 。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。
按照前面的办法就是**分别求出我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。**因为其它数字都出现了两次,在异或中全部抵消掉了。**由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。我们在结果数字中找到第一个为1 的位的位置,记为第N 位。**现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
**现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。**因此到此为止,所有的问题我们都已经解决。这两个只出现一次的数字了。

我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。
因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,
也就是说在这个结果数字的二进制表示中至少有一位为1 。我们在结果数字中找到第一个为1 的位的位置,记为第N 位。现在我们以第N 位
是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
/*
题目为什么要强调有一个数字出现一次,其他的出现两次?
我们想到了异或运算的性质:任何一个数字异或它自己都等于0 。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
 有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。如果能够这样拆分原数组,
 按照前面的办法就是分别求出我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。我们在结果数字中找到第一个为1 的位的位置,记为第N 位。现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
 现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。这两个只出现一次的数字了。
 
 我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。
 因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,
 也就是说在这个结果数字的二进制表示中至少有一位为1 。我们在结果数字中找到第一个为1 的位的位置,记为第N 位。现在我们以第N 位
 是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
 现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。
 
 
 
*/
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        num1[0]=0;
        num2[0]=0;
        int number=0;
        //0^0=0 0^1=1
        /*
        &:如果两个运算数相对应位都是1,那么结果是1,否则为0
        |:如果两个运算数相对应位都是0,那么结果是0,否则为1
        ^:如果两个运算数相对应位位值相同,那么结果是0,否则为1
        */
        for(int i=0;i<array.length;i++){
            number^=array[i];
        }
        // 异或后的数1第一次出现在第几位
        int index=0;
        //与1做&运算,如果个位是1,则结果为1,否则0.
        //比如一个二进制数1110,1110&0001=0000=0
        while((number&1)==0){
            number=number>>1;
            index++;
        }
        //把数组分成了两个子数组,一个子数组里的元素第index位都是1。另一个子数组里的元素第index位都是0.
        for(int i=0;i<array.length;i++){
            boolean flag=((array[i]>>index)&1)==0;
            if(flag){
                num1[0]^=array[i];
            }
            else{
                num2[0]^=array[i];
            }
 
        }
    }
}

41. 整数序列的查找
41.1 和为S的连续整数序列
题目:https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
思路: 双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,
我们根据窗口内值之和来确定窗口的位置和宽度。
虽然双指针或者所谓的滑动窗口技巧还是蛮常见的,但是这一题还真想不到这个思路。 滑动窗口就保证了high的最大值不会越过(sum+1)/2的值。
定义两个指针,分别递增,寻找和为s的序列。

public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
  ArrayList<ArrayList<Integer>> ResultList =new ArrayList<ArrayList<Integer>>(); 
      /*
        滑动窗口问题
		双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,
        我们根据窗口内值之和来确定窗口的位置和宽度。
        虽然双指针或者所谓的滑动窗口技巧还是蛮常见的,但是这一题还真想不到这个思路。
        滑动窗口就保证了high的最大值不会越过(sum+1)/2的值。
      */  
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int low=1;
       int high=2;
       while(low<high){
             //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
         int s=(low+high)*(high-low+1)/2;  
         if(s==sum){
             ArrayList list=new ArrayList();
             //相等,那么就将窗口范围的所有数添加进结果集
             for(int i=low;i<=high;i++){
                 list.add(i);
             }
             ResultList.add(list);
             //以low为下限的过去了
             low++;
             //high不用往回退,因为low++之后,以后的high肯定在之前的high之外。
         }
         //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
         else if(s<sum) high++;
        //如果当前窗口内的值之和大于sum,那么左边窗口右移一下  
         else if(s>sum)  low++;
             
      
       }
              return ResultList;
    }

41.2 求两个乘积最小的数
题目:https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:数列满足递增,设两个头尾两个指针i和j,
若ai + aj == sum,就是答案(相差越远乘积越小)
若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1
若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1
既然是排序好的,就好办了:左右加逼
找到的第一组(相差最大的)就是乘积最小的。可以这样证明:
考虑x+y=C(C是常数),xy的大小。不妨设y>=x,y-x=d>=0,
即y=x+d, 2x+d=C, x=(C-d)/2, x
y=x(x+d)=(C-d)(C+d)/4=(C2-d2)/4,
也就是xy是一个关于变量d的二次函数,对称轴是y轴,开口向下。d是>=0的,d越大, xy也就越小。

    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
      ArrayList<Integer> list=new  ArrayList<Integer>();
              /*
数列满足递增,设两个头尾两个指针i和j,
若ai + aj == sum,就是答案(相差越远乘积越小)
若ai + aj > sum,aj肯定不是答案之一(前面已得出 i 前面的数已是不可能),j -= 1
若ai + aj < sum,ai肯定不是答案之一(前面已得出 j 后面的数已是不可能),i += 1
既然是排序好的,就好办了:左右加逼
找到的第一组(相差最大的)就是乘积最小的。可以这样证明:
考虑x+y=C(C是常数),x*y的大小。不妨设y>=x,y-x=d>=0,
即y=x+d, 2x+d=C, x=(C-d)/2, x*y=x(x+d)=(C-d)(C+d)/4=(C^2-d^2)/4,
也就是x*y是一个关于变量d的二次函数,对称轴是y轴,开口向下。d是>=0的,d越大, x*y也就越小。
      */
       if(array==null)
           return list;
        int low=0;
        int high=array.length-1;
    
        while(low<high){
            int s=array[low]+array[high];
            if(s==sum){
           list.add(array[low]);
           list.add(array[high]);
           return list;    
            }
           else if(s>sum){
              high--;
            }
           else{
                low++;
            }
        }
      //在这个数组中没找到和为sum的一对数,那么返回空的list。
        return list;
    }

42.字符串中字符的移动
42.1 翻转字符串
题目:https://www.nowcoder.com/practice/3194a4f4cf814f63919d0790578d51f3?tpId=13&tqId=11197&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
trim()这个方法可以用于去掉字符串两端的空格。

 public String ReverseSentence(String str) { 
        /*  两种特殊情况
            一种是字符串为空串  另一种是字符串由空字符组成 这两种都返回原字符串即可
        */
        String [] strs=str.split(" ");
        StringBuffer sb=new StringBuffer();
        if(strs.length==0) return str;
        for(int i=strs.length-1;i>0;i--){
            sb.append(strs[i]+" ");
        }
        //注意结尾没空格
        sb.append(strs[0]);
        return sb.toString();
    }

42.2 将字符串循环左移K位
题目:https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析:
我自己第一次做是用队列做的。
更好的方法是通过字符串拼接。
核心:三次翻转字符串 YX=(XTYT)T
应对n>=str.length()的方法是 n=n%str.lengh();
重要知识点:
//str==null ||str.length()0 后者相当于str.equals("")
if(str
null||str.length()==0) return str;
较好的解法:

     public String LeftRotateString(String str,int n) {
           // k比 len 大/一样大  k=k%len
       /*
         abcXYZdef      
        */
        int len= str.length();
         if(len==0)  return str;
        n=n%len;
        StringBuffer sb=new StringBuffer();
         /*
        for(int i=n;i<len;i++){
            sb.append(str.charAt(i));
        }
        for(int i=0;i<n;i++){
            sb.append(str.charAt(i));
        }
        */
         //把前半段接上去
        sb.append(str.substring(n,len));
         //把后半段接上去
        sb.append(str.substring(0,n));
           return  sb.toString();
    }

49. 将字符串转换为整数
题目:
https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
注意 +9999 输出9999
-9999 输出 -9999

    public int StrToInt(String str) {
    if(str.length()==0 || str==null)
           return 0;
    int result=0;
    char c1=str.charAt(0);
    //flag代表是否是负数
    int flag=0;
    //start代表从哪一位开始  当第一位是+或-时,start=1
    int start=0;     
    if(str.equals("-2147483649")||str.equals("2147483648"))
     return 0;
     
    if(c1=='+'){
        start=1;
    }
    if(c1=='-'){
        flag=1;
        start=1;
    }
    for(int i=start;i<str.length();i++){
        char c=str.charAt(i);
     /*
        a.怎么判断一个字符是否是数字。c<='9'&&c>='0'就是数字。或者用
        (c1+"").matches("[0-9]")
        b.怎么把char字符转成对应的int (int)(c-'0')
        c.怎么把每一位数字累加上去 result=result*10+((int)(c-'0'))
      */
        if(c>'9'||c<'0') return 0;
        result= result*10+((int)(c-'0'));
    }
      if(flag==1) return -result;
      else  return result;
    
}

51. 找出重复的数
题目:https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:这个题的意思是把找到的第一个重复数字放进duplication[0]。
用一个辅助空间HashSet来存储数组中元素,判断是否重复。
如果是要统计一个集合中各个元素的个数,一般用HashMap。
如果是要统计一个集合中各个元素是否重复,用HashSet就行了。

 public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null||length==0)
            return false;
        Set set=new HashSet();
        for(int i=0;i<numbers.length;i++){
            if(set.contains(numbers[i])){
                duplication[0]=numbers[i];
                return true;  
            }
              else{
                  set.add(numbers[i]);
                  
            }
        }
        return false;
    }

52. 构建乘积数组
题目:https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:将B[i]的求解分成两部分,分别求乘积A[0]A[1]…*A[i-1]和A[i+1]A[i+2]…*A[n-1]

public class Solution {
    /*
思路:使用矩阵法求解,将矩阵分为上三角矩阵和下三角矩阵,分别求乘积
解释下代码,设有数组大小为5。
对于第一个for循环
第一步:b[0] = 1;
第二步:b[1] = b[0] * a[0] = a[0]
第三步:b[2] = b[1] * a[1] = a[0] * a[1];
第四步:b[3] = b[2] * a[2] = a[0] * a[1] * a[2];
第五步:b[4] = b[3] * a[3] = a[0] * a[1] * a[2] * a[3];
然后对于第二个for循环
第一步
temp *= a[4] = a[4]; 
b[3] = b[3] * temp = a[0] * a[1] * a[2] * a[4];
第二步
temp *= a[3] = a[4] * a[3];
b[2] = b[2] * temp = a[0] * a[1] * a[4] * a[3];
第三步
temp *= a[2] = a[4] * a[3] * a[2]; 
b[1] = b[1] * temp = a[0] * a[4] * a[3] * a[2];
第四步
temp *= a[1] = a[4] * a[3] * a[2] * a[1]; 
b[0] = b[0] * temp = a[4] * a[3] * a[2] * a[1];
由此可以看出从b[4]到b[0]均已经得到正确计算。
    */
    public int[] multiply(int[] A) {
              if(A==null)
                  return null;
            int length=A.length;  
            int B[]=new int[length];
            if(length==0)
                return B;
        /*
        计算一半。A[0]*A[1]*...*A[i-1]
        */
        B[0]=1;
        for(int i=1;i<length;i++){
            B[i]=B[i-1]*A[i-1];
        }
         /*
        计算另一半。A[i+1]*A[i+2]*...*A[n-1]
        */
        int temp=1;
        for(int i=length-2;i>=0;i--){
            temp=temp*A[i+1];
            B[i]=B[i]*temp;
        }
          return B;
    }
}

53. 正则表达式匹配
https://www.nowcoder.com/questionTerminal/45327ae22b7b413ea21df13ee7d6429c?f=discussion

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

54. 表示数值的字符串
题目:https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

     /*
    逐个字符进行判断,
    1.e或E和小数点最多出现一次,而e或E的前一个必须是数字,且不能是第一个或最后一个字符
    2.小数点最多出现一次  且不能出现在e或者E后面 即12e+4.3 不行
    3.非开头的+或者-的前一个字符只能是e或E。 123.45e+6   -1E-16
     */
    public boolean isNumeric(char[] str) {
         if(str.length==0)
                return false;
         //用于判断第一个符号是不是+或者-
         int start=0;
         //记录小数点个数
         int pcount=0;
         //记录e或者E的个数
        int ecount=0;
        if(str[0]=='+'||str[0]=='-')
             start=1;
        for(int i=start;i<str.length;i++){
            if(str[i]=='e'||str[i]=='E'){
                ecount++;
                if(ecount>1) return false;
                if(i==0||i==str.length-1)  return false;
                if(str[i-1]<'0'||str[i-1]>'9') return false;
                //e或者E后面不能再出现.
                pcount++;
            }
           else if(str[i]=='.'){
               pcount++;
               if(pcount>1) return false;
              //  if(i==0||i==str.length-1)  return false;
              // if(str[i-1]<'0'||str[i-1]>'9') return false;
            }
            else if(str[i]=='+'||str[i]=='-'){
               if(str[i-1]!='e'&&str[i-1]!='E') return false;
            }
           else if(str[i]<'0'||str[i]>'9')  return false;

        }
           return true;
    }

55. 字符流中第一个不重复的字符
题目:https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

public class Solution {
   
    /*
    用例:"google"
      对应输出应该为:
      "ggg#ll" 
      LinkedHashMap是HashMap的一个直接子类,二者唯一的区别是LinkedHashMap在HashMap的基础上,采用双向链表(doubly-linked list)的形式将所有entry连接起来,
      这样是为保证元素的迭代顺序跟插入顺序相同。取出的keySet()仍然是按照插入顺序有序的
    */
    LinkedHashMap<Character,Integer> map=new LinkedHashMap<>();
    //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()
    {
     Set<Character> set= map.keySet();
       for(char key:set){
           if(map.get(key)==1)
               return key;
       }
      return '#';  
    }
}

或者用StringBuffer来记录插入顺序:

import java.util.*;
public class Solution {
    StringBuffer sb=new StringBuffer();
    Map<Character,Integer> map=new HashMap();
    /*
    用例:"google"
      对应输出应该为:
      "ggg#ll"
    */
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        sb.append(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(int i=0;i<sb.length();i++){
             char c=sb.charAt(i);
             if(map.get(c)==1) return c;
         }
          return '#';
         
    }
}

56.链表中环的入口节点
题目:https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
定义一快一慢两个指针,快的走的是慢的的两倍,直到他俩相遇,之后让快的从头开始走,慢的从相遇点走,走的速度一样,最终会在环的入口处相遇。
https://www.nowcoder.com/questionTerminal/253d2c59ec3e4bc68da16833f79a38e4?f=discussion在这里插入图片描述
详解:https://www.nowcoder.com/questionTerminal/253d2c59ec3e4bc68da16833f79a38e4?f=discussion
假设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者会相遇,且恰好相遇在环的入口点。

public class Solution {
   public ListNode EntryNodeOfLoop(ListNode pHead) {
       if(pHead==null)
             return null;
       ListNode fast=pHead;
       ListNode slow=pHead;
     while(fast!=null && fast.next!=null){
         slow=slow.next;
         //fast走得是slow的两倍快
         fast=fast.next.next;
         if(fast==slow){
             fast=pHead;
             while(fast!=slow){
                 fast=fast.next;
                 slow=slow.next;
             }
             if(fast==slow)
                  return slow;
         }
     }
     //不存在环
           return null;
    }
}

47.不用加减乘除做加法
题目:https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
首先补充java的位运算符:
http://www.runoob.com/java/java-operators.html在这里插入图片描述

Test.java 文件代码:
public class Test {
  public static void main(String[] args) {
     int a = 60; /* 60 = 0011 1100 */ 
     int b = 13; /* 13 = 0000 1101 */
     int c = 0;
     c = a & b;       /* 12 = 0000 1100 */
     System.out.println("a & b = " + c );
 
     c = a | b;       /* 61 = 0011 1101 */
     System.out.println("a | b = " + c );
 
     c = a ^ b;       /* 49 = 0011 0001 */
     System.out.println("a ^ b = " + c );
 
     c = ~a;          /*-61 = 1100 0011 */
     System.out.println("~a = " + c );
 
     c = a << 2;     /* 240 = 1111 0000 */
     System.out.println("a << 2 = " + c );
 
     c = a >> 2;     /* 15 = 1111 */
     System.out.println("a >> 2  = " + c );
  
     c = a >>> 2;     /* 15 = 0000 1111 */
     System.out.println("a >>> 2 = " + c );
  }
} 

解析②:
 (1)十进制加法分三步:(以5+17=22为例)

  1. 只做各位相加不进位,此时相加结果为12(个位数5和7相加不进位是2,十位数0和1相加结果是1);

  2. 做进位,5+7中有进位,进位的值是10;

  3. 将前面两个结果相加,12+10=22

(2)这三步同样适用于二进制位运算

1.不考虑进位对每一位相加。0加0、1加1结果都是0,0加1、1加0结果都是1。这和异或运算一样;

2.考虑进位,0加0、0加1、1加0都不产生进位,只有1加1向前产生一个进位。可看成是先做位与运算,然后向左移动一位;

3.相加过程重复前两步,直到不产生进位为止。
解析见:https://www.cnblogs.com/smile233/p/8511546.html

public class Solution {
    /*
(1)十进制加法分三步:(以5+17=22为例)

1. 只做各位相加不进位,此时相加结果为12(个位数5和7相加不进位是2,十位数0和1相加结果是1);

2. 做进位,5+7中有进位,进位的值是10;

3. 将前面两个结果相加,12+10=22

 (2)这三步同样适用于二进制位运算

1.不考虑进位对每一位相加。0加0、1加1结果都是0,0加1、1加0结果都是1。这和异或运算一样;

2.考虑进位,0加0、0加1、1加0都不产生进位,只有1加1向前产生一个进位。可看成是先做位与运算,然后向左移动一位;

3.相加过程重复前两步,直到不产生进位为止。
    
    */
    public int Add(int num1,int num2) {
  
  while(num2!=0){
      //逐位异或,一样是0,不一样(0,1)是1.相当于不考虑进位的加
         int tmp=num1^num2;
      //与运算再左移一位,相当于考虑进位,num2存储进位
         num2=(num1&num2)<<1;
         num1=tmp;       
        }
        return num1;
    }
}

46. 求1到n的和
题目:https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=4&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
&& 运算,当前面为假时,后面自动不算。
解题思路:
1.需利用逻辑与的短路特性实现递归终止。
2.当sum==0时,(sum>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;
3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。

   public int Sum_Solution(int n) {
   /*
   && 运算,当前面为假时,后面自动不算。
    解题思路:
1.需利用逻辑与的短路特性实现递归终止。 
2.当sum==0时,(sum>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;
3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。
        */
       int sum=n;
       boolean result=n>1&&(sum+=Sum_Solution(n-1))>0;
        return sum;
    }

57.删除链表中的重复结点
题目:https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

  public ListNode deleteDuplication(ListNode pHead)
    {
         if(pHead == null)
                return null;
         //新建一个头结点
        ListNode head=new ListNode(1);
        head.next=pHead;
        //pre 代表当前结点的前一个结点  node代表当前结点的下一个结点
         ListNode pre= head;
         ListNode node=pHead;
        while(node!=null && node.next!=null){
            //没遇上重复结点的时候,向后遍历
            if(node.val != node.next.val){
                pre=node;
                node=node.next;
            }
            //遇上重复结点
           else if(node.val == node.next.val){
               int v=node.val;
            while(node!=null&&node.val==v){
                node=node.next;
            }
               //上一个非重复值指向下一个非重复值,即删除重复结点
             pre.next=node;
            }
        }
        return head.next;
    }

58. 二叉树的下一个节点
题目:https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:找出访问了当前结点后,下一个应当被访问的结点。
1.当前结点有右子树。找出右子树中位于最左下方的结点。
左子树就不用判断了,中根遍历肯定已经访问过左子树了
2.右子树为空的情况:
如果当前结点是它父结点的左孩子结点,
那么接下来访问的是父结点。
如果当前结点是它父结点的右孩子结点
回退到其父结点,再进行刚才的判断

   //找出访问了当前结点后,下一个应当被访问的结点
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode==null)
            return null;
      /*
      1.当前结点有右子树。找出右子树中位于最左下方的结点。
      左子树就不用判断了,中根遍历肯定已经访问过左子树了
      */
     if(pNode.right!=null){
         pNode=pNode.right;
         while(pNode.left!=null){
             pNode=pNode.left;
         }
         return pNode;
     }   
       /*
 右子树为空的情况:
 2.如果当前结点是它父结点的左孩子结点,
 那么接下来访问的是父结点。
 如果当前结点是它父结点的右孩子结点
 回退到其父结点,再进行刚才的判断
 */  
       while(pNode.next!=null){
     if(pNode.next.left==pNode){
         return pNode.next;
     }
    if(pNode.next.right==pNode){
         pNode=pNode.next;
     }           
     }   
     return  null; 
    }

59. 对称的二叉树
题目:https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
解析: 判断某结点的左右子树是否对称:
利用递归进行判断,如果左右子树节点的值相等,
且左子树的左孩子等于右子树的右孩子 且左子树的右孩子等于右子树的左孩子,
则是对称的。
相关题目:二叉树的镜像翻转。
https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking

   boolean isSymmetrical(TreeNode pRoot)
    {
     if(pRoot==null)  return true;   
        return common(pRoot.left,pRoot.right);
    }
    /*
    判断某结点的左右子树是否对称。
    利用递归进行判断,如果左右子树节点的值相等,
    且左子树的左孩子等于右子树的右孩子  且左子树的右孩子等于右子树的左孩子,
    则是对称的。
    */
    static boolean common(TreeNode left,TreeNode right){
  if(left==null&&right==null)
            return true;
        if(left!=null&&right!=null){
            return (left.val==right.val)&&common(left.left,right.right)
                &&common(left.right,right.left);
        }
        return false;
    }

60. 按之字形顺序打印二叉树
**题目:**https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
**思路:**利用两个栈的辅助空间分别存储奇数偶数层的节点,然后打印输出。有点类似层次遍历。
这里用栈是因为栈是先进后出的数据结构,上一层先入栈的子结点会在下一层后被访问。
1.定义两个栈,分别存放奇数层和偶数层的结点
2.flag标志变量,等于1,从左往右。等于0,从右往左。
3.先将根结点入栈1
4.flag1 若栈1非空,栈顶元素循环出栈,将栈顶元素添加到list。并将出栈元素的左孩子和右孩子入栈2。
5.flag
0 若栈2非空,栈顶元素循环出栈,将栈顶元素添加到list。出栈元素的右孩子和左孩子入栈1。

    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
            ArrayList<ArrayList<Integer>> res=new  ArrayList<ArrayList<Integer>>();
            if(pRoot==null)  return res;
            //每行单独放一个list
            //后进先出 建两个栈,分别存放奇数层和偶数层
            Stack<TreeNode> s1=new Stack();
            Stack<TreeNode> s2=new Stack();
            int flag=1;
     //根结点入栈
            s1.push(pRoot);
            while(!s1.isEmpty()|| !s2.isEmpty()){
                //用于存放该层数据的list
                ArrayList<Integer> list=new ArrayList<Integer>();
                if(flag==1){
                 while(!s1.isEmpty()){
                     TreeNode node=s1.pop();
                     list.add(node.val);
            //一定要判断左右结点是否为空再压栈,否则会发生空指针异常(访问空结点的val  left  right)
                     if(node.left!=null)
                       s2.push(node.left);
                     if(node.right!=null)
                         s2.push(node.right);
                 }
                 res.add(list);
                 flag=0;
                    //一层结束,进入下次循环
                 continue;    
                }
               if(flag==0){
                   while(!s2.isEmpty()){
                       TreeNode node=s2.pop();
                       list.add(node.val);
                       if(node.right!=null)
                           s1.push(node.right);
                       if(node.left!=null)
                           s1.push(node.left);
                   }
                   flag=1;
                   res.add(list);
                   continue;
                }
            }
              return res;
     }

61. 把二叉树打印成多行
题目:https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:层次遍历二叉树。
这里有个问题是要逐层输出。可以用end来计当前层结点数
start初始值为0,每访问一个当前层结点,start++。当end==start,当前层访问结束。

  • 按层次输出二叉树
  • 根节点入队。
  • 当队列不空的时候,重复以下操作。
  • 1、弹出一个元素。作为当前的根节点。访问当前结点。
  • 2、如果左孩子非空,左孩子入队。
  • 3、如果右孩子非空,右孩子入队。
/*
层次遍历二叉树。
这里有个问题是要逐层输出。可以用end来计当前层结点数
start初始值为0,每访问一个当前层结点,start++。当end==start,当前层访问结束。
 * 思路:
 * 按层次输出二叉树
  * 根节点入队。
 * 当队列不空的时候,重复以下操作。
 * 1、弹出一个元素。作为当前的根节点。访问当前结点。
 * 2、如果左孩子非空,左孩子入队。
 * 3、如果右孩子非空,右孩子入队。
*/
import java.util.*;
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
    if(pRoot==null)
        return res;
    Queue<TreeNode> q=new LinkedList<TreeNode>();
        //根结点入队
     q.offer(pRoot); 
        /*每访问一个当前层结点,start++,end是当前层总结点数
        */
    int start=0;
    int end=1;       
   //列表用于存放本层结点
  ArrayList<Integer> list=new ArrayList<Integer>();
         
            while(!q.isEmpty()){
                TreeNode node=q.poll();
                list.add(node.val);
                start++;
                if(node.left!=null)
                    q.offer(node.left);
                if(node.right!=null)
                    q.offer(node.right);
        if(start==end){
           start=0;
            //end置为下一层结点数量
           end=q.size();
//注意这里一定要 new ArrayList<>(list)  否则会添加进来的都是空list
//不重新new的话从始至终resultList中所有引用都指向了同一个list
//直接添加的话,后面对list的clear操作就会影响resultList里面的元素了
           res.add(new ArrayList<>(list));
           list.clear();    
           //clear()移除列表中的每一个元素 
        }     
        }
         
        return res;   
    }
     
}

62.序列化二叉树
题目:https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
序列化:前序遍历二叉树存入字符串中 ;用(#)表示空结点,以,表示一个结点值的结束(value,)
反序列化:根据前序遍历重建二叉树。
由标名空子树的先根遍历序列建立一棵二叉树。
这个算法在书上第170页。步骤:
从标名空子树的先根遍历序列依次读入字符,若读入的字符是‘#’,则建立空树,否则:
1.建立根结点
2.继续建立树的左子树
3.继续建立树的右子树

public class Solution {
    StringBuffer sb=new StringBuffer();
    String Serialize(TreeNode root) {
      if(root!=null){
          sb.append(root.val);
          sb.append(",");
          Serialize(root.left);
          Serialize(root.right);
      }
      if(root==null)
        sb.append("#,");
         return sb.toString();
    }
    public int index=0;
    TreeNode Deserialize(String str) {
      String strs[]= str.split(",");
     String s=strs[index];
     index++;
     TreeNode node=null;
     if(!s.equals("#")){
         node=new TreeNode(Integer.valueOf(s));
         node.left=Deserialize(str);
         node.right=Deserialize(str);
     }
     return node;
    }
}

63. 求二叉搜索树的第K个节点
题目:https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。
所以,按照中序遍历顺序找到第k个结点就是第k小的值。
这个题写法参照课本p158 中序遍历递归写法。
二叉搜索树(二叉排序树):
或者是一棵空树,或者具有下列性质:
1.若左子树不空,则左子树所有结点值均小于根结点
2.若右子树不空,则右子树所有结点值均大于根结点

public class Solution {
    int count = 0;
    public TreeNode KthNode(TreeNode pRoot, int k) {
     if (k < 1)       
         return null;
  if(pRoot!=null){  
    TreeNode leftNode = KthNode(pRoot.left,k);   
    if (leftNode != null) {
        return leftNode;
    }
     count++;
     if (count == k) {       
        return pRoot;   
    }
    TreeNode rightNode = KthNode(pRoot.right,k);   
    if (rightNode != null){
         return rightNode;
    }        
     }
    return null;
}
}

64. 数据流中的中位数
题目:https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=4&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
剑指offer上用到的是优先级队列。这种解法有空研究一下。我用的是用集合类的排序方法。Collections.sort()

ArrayList<Integer> list=new ArrayList<Integer>();
    public void Insert(Integer num) {
    list.add(num);     
    }

    public Double GetMedian() {
        //集合排序
      Collections.sort(list);
        // 0 1 2 3 4
   if(list.size()%2!=0) 
       return list.get(list.size()/2)/1.0;
        //0 1 2 3 4 5 
   else{
      double result=(list.get(list.size()/2)+list.get(list.size()/2 - 1))/2.0;
      return result;  
   }
          
    }


65. 滑动窗口的最大值
题目:https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=3&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
思路:两个for循环,第一个for循环滑动窗口,第二个for循环滑动窗口中的 值,寻找最大值。

     public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
          ArrayList<Integer> list=new ArrayList<>();
          if(num.length==0 || size==0)  return list;
          int low=0;
          int high=size-1;
       while(high<num.length){
          int max=num[low];
          for(int i=low+1;i<=high;i++){
              if(num[i]>max){
                  max=num[i];
              }
          }
           list.add(max);
          low++;
          high++;
       }
           return list;
    }

66.矩阵中的路径
https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

基本思想:
0.根据给定数组,初始化一个标志位数组,初始化为false,表示未走过,true表示已经走过,不能走第二次
1.根据行数和列数,遍历数组,先找到一个与str字符串的第一个元素相匹配的矩阵元素,进入helper
2.根据i和j先确定一维数组的位置,因为给定的matrix是一个一维数组
3.确定递归终止条件:越界,当前找到的矩阵值不等于数组对应位置的值,已经走过的,这三类情况,都直接false,说明这条路不通
4.若k,就是待判定的字符串str的索引已经判断到了最后一位,此时说明是匹配成功的
5.下面就是本题的精髓,递归不断地寻找周围四个格子是否符合条件,只要有一个格子符合条件,就继续再找这个符合条件的格子的四周是否存在符合条件的格子,直到k到达末尾或者不满足递归条件就停止。
6.走到这一步,说明本次是不成功的,我们要还原一下标志位数组index处的标志位,进入下一轮的判断。

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        //初始化标志位数组  标记该位置是否被访问
       boolean flag[]=new boolean[matrix.length];
       //循环遍历二维数组,找到起点等于str第一个元素的值,再递归判断四周是否有符合条件的----回溯法
       for(int i=0;i<rows;i++){
           for(int j=0;j<cols;j++){
               if(helper(matrix,rows,cols,i,j,flag,str,0)){
                   return true;
               }
           }
       }
           return false;
    }
    //helper(初始矩阵,矩阵行数,矩阵列数,索引行坐标i,索引纵坐标j,初始化标志位数组,待判断的字符串,字符串索引 初始为0即先判断字符串的第一位)
    public boolean helper(char[] matrix,int rows,int cols,int i,int j,boolean flag[],char[]str,int k){
        //先根据i和j计算匹配的元素转为一维数组的位置
          int index=i*cols+j;
          //递归终止条件   不能越界,不能不等,不能已访问
        if(i<0||j<0||i>=rows||j>=cols||matrix[index]!=str[k]||flag[index]==true||flag[index]==true){
            return false;
        }
        //若k已经到达str末尾了,说明之前的都已经匹配成功了,直接返回true即可
        if(k==str.length-1)
              return true;
        flag[index]=true;
        //往上下左右四个方向递归寻找,找到1条就行
        if(helper(matrix, rows,cols,i-1,j,flag,str,k+1) ||
                helper(matrix, rows,cols,i+1,j,flag,str,k+1) ||
                helper(matrix, rows,cols,i,j-1,flag,str,k+1) ||
                helper(matrix, rows,cols,i,j+1,flag,str,k+1)){
            return true;
        }
          flag[index]=false;
          return false;
    }
}

67. 机器人的运动范围
https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=4&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
这里的格子总数是指,只要能到达的都算

public class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        if(threshold<0)
            return 0;
        //标志位数组,标志该位是否被访问
        boolean flag[][]=new boolean[rows][cols];
        return helper(threshold, rows, cols,0,0,flag);
    }

public int helper(int threshold, int rows, int cols,int i,int j,boolean flag[][]){
    //递归终止条件 不能越界 不能超过k,不能算已经走过的格子
    if(i<0 || j<0 || i>=rows || j>=cols || numsum(i)+numsum(j)>threshold || flag[i][j]==true){
        return 0;
    }
    //某格子已经被访问过
    flag[i][j]=true;
    return helper(threshold,rows,cols,i-1,j,flag)+
            helper(threshold,rows,cols,i+1,j,flag)+
            helper(threshold,rows,cols,i,j-1,flag)+
            helper(threshold,rows,cols,i,j+1,flag)+1;

}
public int numsum(int i){
        int sum=0;
        while(i>0){
            sum+=i%10;
            i=i/10;
        }
        
        return sum;
}
}

68.剪绳子
题目:https://www.nowcoder.com/practice/57d85990ba5b440ab888fc72b0751bf8?tpId=13&tqId=33257&tPage=4&rp=4&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
链接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion
来源:牛客网

  • 题目分析:
  • 先举几个例子,可以看出规律来。
  • 4 : 2*2
  • 5 : 2*3
  • 6 : 3*3
  • 7 : 223 或者4*3
  • 8 : 233
  • 9 : 333
  • 10:2233 或者43*3
  • 11:233*3
  • 12:333*3
  • 13:22333 或者433*3
  • 下面是分析:
  • 首先判断k[0]到k[m]可能有哪些数字,实际上只可能是2或者3。
    public int cutRope(int n) {
       // n<=3的情况,m>1必须要分段,例如:3必须分成1、2;1、1、1 ,n=3最大分段乘积是2,
        if(n==2)
            return 1;
        if(n==3)
            return 2;
        //dp[i]指当绳子长度为i时的最大乘积  1 2 3意外
        int[] dp = new int[n+1];
        /*
        下面3行是n>=4的情况,跟n<=3不同,4可以分很多段,比如分成1、3,
        这里的3可以不需要再分了,因为3分段最大才2,不分就是3。记录最大的。
         */
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        int res=0;//记录最大的
        for (int i = 4; i <= n; i++) {
            for (int j = 1; j <=i/2 ; j++) {
                res=Math.max(res,dp[j]*dp[i-j]);
            }
            dp[i]=res;
        }
        return dp[n];
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值