剑指offer NO.1(2018.12.25——2018.1.25)

 public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) {//注意while处理多个case
            int a = in.nextInt();
            int b = in.nextInt();
            System.out.println(a + b);
        }
    }

1. 二维数组中的查找

  • 题意:二维数组从左至右递增,从上至下递增,给一个数判断是否在里面

  • 思路:找到数组的规律,从左下角开始找最好,如果目标比他大,向右移动;如果比他小,向左移动。

public class Solution {
    public boolean Find(int target, int [][] array) {
        int a = array.length;
        int b = array[0].length; 
        for(int i = a-1,j= 0;i>=0&&j<b;){
            if(array[i][j]>target ){
                i--;
            }
            else if(array[i][j]<target){
                j++;
            }
            else{
                return true;
            }            
         }
        return false;
    }
}

2. 替换空格:

  • 题目:将一个字符串的所有空格转变成“20%”
  • 思路:stringbuffer 先计算有几个空格,然后从后往前进行改变,这样就只需要一次了。
public static String replaceSpace(StringBuffer str) {
    	int l = str.length();
    	int space = 0;
    	for(int i = 0;i<l;i++) {
    		if(str.charAt(i)==' ') {
    			space++;
    		}
    	}
    	space = 2*space;
    	str.setLength(l+space);   	
    	for(int i = l-1,j = l-1+space;i>=0;) {    		
    		if(str.charAt(i)!=' ') {
    			str.setCharAt(j, str.charAt(i));
    			i--;
    			j--;
    		}
    		else {
    			str.setCharAt(j,'0');  
    			j--;
    			str.setCharAt(j,'2');    
    			j--;
    			str.setCharAt(j,'%');
    			j--;
    			i--;    			
    		}
    		
    	}
    	String res = str.toString();    		
		return res;
    }

3. 从尾到头打印链表

  • 输入一个链表,从尾到头输出
  • 思路1:将数据放入堆栈,,最后pop出来,即可实现
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
	   ArrayList<Integer> res = new ArrayList<Integer>();
		Stack<Integer> s = new Stack<Integer>();
	    while(listNode != null) {
	    	s.push(listNode.val);
	    	listNode = listNode.next;
	    }
	    
	    while(!s.isEmpty()) {
	    	res.add(s.pop());
	    }
	    return res;
	}
}
  • 思路2:递归的方法(第二遍再写)MARK

4. 用两个栈实现队列

  • 题意:队列先进先出,栈先进后出
  • 思路:进入的时候向A栈放,出去的时候A放到B里面,从B出栈
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(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

5. 旋转数组的最小数字

  • 思路: 理解 非减排序 == 递增排序; 得到一个递增排序旋转,然后去找最小值,二分法。
  • 如果没有重复值的话:mid值大于等于start,则最小值在第二部分;否则最小值在第一部分。最后end就为最小值
  • 如果有重复值的话,如101111111,111111101
    此时需要判断min等于第一个值和最后一个值——只能顺序查找
	public static int minNumberInRotateArray(int [] array) {
       
		int len = array.length;
		if (len == 0) {
        	return 0;
        }
        else {      		
        	int[] res = new int[2];
        	res = binary(0,len-1,array);
        	while(res[1] - res[0] !=1 && res[0] != -1){      		
        		res = binary(res[0],res[1],array); 	
        	}
        	if(res[0] == -1){
        		int min = array[0];
        		for(int i = 0;i<len;i++) {
        			if(min>array[i]) {
        				min = array[i];
        			}
        		}
        		return min;
        	}
        	else{
        		return array[res[1]]<array[res[0]]?array[res[1]]:array[res[1]];
        	}
        		
        	}
        }	
	public static int[] binary(int start, int end,int [] a){	
			int[] res1 = new int[2]; 
			int mid = (start+end)/2;
			if(a[mid] == a[start] && a[mid] == a[end]) {
				res1[0] = -1;
				res1[1] = -1;
				return res1;				
				
	    	}else if(a[mid]>=a[start]){
	    		res1[0] = mid;
				res1[1] = end;		
				return res1;
	    	}
	    	else{	
	    		res1[0] = start;
				res1[1] = mid;		
				return res1;
	    	}
		}

6. 斐波那契数列

  • 思路:就很简单。。。。;补充给一个值找到他是第几项
public static int Fibonacci(int n) {
		int a = 0,b = 1,c;
		if(n == 0) {
			return 0;
		}else {
			for(int i =0;i<n-1;i++) {
				c = a+b;
				a = b;
				b = c;
			}
			return b;
		}
		

6. 斐波那契变形!!

Fibonacci数列是这样定义的: F[0] = 0 F[1] = 1 for each i ≥ 2: F[i] = F[i-1] +
F[i-2] 因此,Fibonacci数列就形如:0, 1, 1, 2, 3, 5, 8, 13,
…,在Fibonacci数列中的数我们称为Fibonacci数。给你一个N,你想让其变为一个Fibonacci数,每一步你可以把当前数字X变为X-1或者X+1,现在给你一个数N求最少需要多少步可以变为Fibonacci数。

输入描述: 输入为一个正整数N(1 ≤ N ≤ 1,000,000)

输出描述: 输出一个最小的步数变为Fibonacci数"

import java.util.Scanner;
public class Main{
    
    public static void main(String[] args) {
    Scanner i = new Scanner(System.in);
	System.out.println(FibI(i.nextInt()));
}


    public static int FibI(int n) {
		int a = 0,b = 1,c;
		while(true) {
			if(n>=a && n<=b) {
				break;
			}		
			c = a+b;
			a = b;
			b = c;
			
		}
		return n-a>b-n?b-n:n-a;
		
	}
    
    
}

6. 跳台阶

题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

  • 思路:通过列举发现,n = 1,一种;n = 2,两种。只有1步和2步,则跳到n的话,只可能n-1位置跳1步过去;n-2位置跳2步过去
	public static int JumpFloor(int target) {
        if(target<=2) {
        	return target;
        }
        return JumpFloor(target-2) +JumpFloor(target-1);
    

6. 变态跳台阶:

题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

  • 思路此时n的大小等于,f(n-1)+…f(1)+1,基础部分就是f(1)= 1,递归即可—
public static int JumpFloorII(int target) {
		if(target<=2) {
        	return target;
        }
		int res = 0;
		while(target>1) {
			res += JumpFloorII(target-1);
			target --;
		}
		return res+1;
		
    }

7. 矩形覆盖

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

public class Solution {
    public int RectCover(int target) {
        if(target<=2){
            return target;
        }
        return RectCover(target-1)+RectCover(target-2);
    }
}

8. 二进制中1的个数

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

  • 思路:计算机里面二进制存储各个数字,负数用补码;所以就是通过位运算,得到数字中的1的个数。int32位,通过一个数(0x 80000000)来循环右移得到二进制表示。
public int NumberOf1(int n) {
        int count = 0;
        for (int i = 0; i < 32; i++)
        {
            int t = (n & 0x80000000 >>> i) >>> (31 - i);
            if(t == 1){
                count++;
            }
        }
        return count;
    }
  • 巧妙方法
public int NumberOf1(int n) {
        int count = 0;
        while(n!= 0){
            count++;
            n = n & (n - 1);
         }
        return count;
    }

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

9. 数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

public double Power(double base, int exponent) {
        double res=1.0;
        if(exponent>=0){
            for(int i=0;i<exponent;i++){
            res =res*base;
        }           
        }
        else{
            base = 1.0/base;
            for(int i=0;i<-exponent;i++){
                res =res*base;
            }
        }
        return res;
  }

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

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

  • 思路1:空间复杂度O(n),空间换时间
public static void reOrderArray(int [] array) {
        int len = array.length,j=0;
        int[] res = new int[len];
        res = array.clone();	
        for(int i=0;i<len;i++) {
        	
        	if(res[i]%2!=0) {
        		array[j] = res[i];
        		j++;
        		
        	}    	
        }
        for(int i=0;i<len;i++) {
        	if(res[i]%2==0) {
        		array[j] = res[i];
        		j++;
        	}
        }
        
    }
  • 思路2:O(n2), 从后往前,n次遍历。每次判断相邻的,前奇数后偶数;这样可以实现奇数都在最前面,并且是按顺序的。

 for (int i = 0; i < array.size();i++)
        {
            for (int j = array.size() - 1; j>i;j--)
            {
                if (array[j] % 2 == 1 && array[j - 1]%2 == 0) //前偶后奇交换
                {
                    swap(array[j], array[j-1]);
                }
            }
        }
    }

11. 链表倒数第k个结点

题目描述 输入一个链表,输出该链表中倒数第k个结点。

  • 思路1:遍历两次,一次计算总共长度,第二次再遍历到倒数第k个结点
public ListNode FindKthToTail(ListNode head,int k) {

        ListNode r = head;
        int l = 1;
        if(head == null | k<0){
            return null;
        }
        while(r.next != null){
            r = r.next;
            l++;
        }
        if(l<k)
            return null;
        for(int i =0;i<=l-k-1;i++){
            head = head.next;
        }
        return head;

    }
  • 思路2:遍历一遍,用两个指针即可
public ListNode FindKthToTail(ListNode head,int k) {

        ListNode a = head;
        ListNode b = head;
        if(head == null|k<0){
            return null;
        }
        int i = 0;
        while(a!=null & i<k){
            i++;
            a = a.next;
        }
        if(i!=k){
            return null;
        }
        while(a!=null){
            b = b.next;
            a = a.next;
        }
        return b;


    }

12. 反转链表

输入一个链表,反转链表后,输出新链表的表头。

  • 思路1:很笨的方法,将链表反转,头结点就是最后一个,先得到最后一个结点,然后再倒着将所有的结点val值加入。O(n2)
public ListNode ReverseList(ListNode head) {
        if(head == null||head.next == null){
            return head;
        }
        ListNode a = head;
        int l = 1;
        while(a.next!= null){
            a = a.next;
            l++;
        }
        ListNode res = a;
        for(int i=l-2;i>=0;i--){
            ListNode b = head;
            for(int j=0;j<i;j++){
                b = b.next;
            }
            ListNode C = new ListNode(b.val);
            a.next = C;
            a = a.next;
        }
        return res;
    }
  • 思路2:简单方法!!!只进行一遍遍历,记录当前结点的前一个、后一个。
    先记录当前结点的后一个,然后将结点的后一个赋值为前一个;
    再将当前结点变为后一个,前一个结点变为当前结点
public ListNode ReverseList(ListNode head) {
        ListNode pre = null;
        ListNode behind ;
        if(head == null||head.next == null){
            return head;
        }
        while(head!=null){
            behind = head.next;
            head.next = pre;

            pre = head;
            head = behind;
        }
        return pre;
    }

13. 合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

  • 思路1:简单法(非递归法),两个链表进行一次遍历,小的值先放进新链表中,直到两个链表值都取了
public static ListNode Merge(ListNode list1,ListNode list2) {
        ListNode res = new ListNode(0);
        ListNode a = new ListNode(0);
        a.next = res;
        while(list1 !=null || list2 !=null){
            if(list1 ==null && list2 !=null){
                res.next = list2;
                list2 = null;
            }else if(list1 !=null && list2 ==null){
                res.next = list1;
                list1 = null;
            }else{
                if(list1.val<list2.val){
                    ListNode C = new ListNode(list1.val);
                    res.next = C;
                    res = res.next;
                    list1 = list1.next;
                }else{
                    ListNode C = new ListNode(list2.val);
                    res.next = C;
                    res = res.next;
                    list2 = list2.next;
                }

            }

        }
        return res.next.next;

    }
  • 思路2:递归法,重复调用!!!!!!!!!!!!!
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;
                }
    }

14.顺时针打印矩阵

  • 思路1:ZZ做法,复杂极其
public ArrayList<Integer> printMatrix(int [][] matrix) {
        int a = matrix.length;
        int b = matrix[0].length;
        ArrayList res = new ArrayList();
        int start = 0,end = a-1;
        int flag = 0;
        if(a == 1){
            for(int j=0;j<b;j++){
                res.add(matrix[0][j]);
            }
            return res;
        }
        if(b == 1){
            for(int i=0;i<a;i++){
                res.add(matrix[i][0]);
            }
            return res;
        }
        while(start!=end){

            if(end-start==1){
                for(int j =flag;j<=b-1-flag;j++){
                    res.add(matrix[start][j]);
                }
                for(int j =b-1-flag;j>=flag;j--){
                    res.add(matrix[end][j]);
                }
                return res;
            }
            for(int j =flag;j<=b-1-flag;j++){
                res.add(matrix[start][j]);
            }
            for(int i=start+1;i<end;i++){
                res.add(matrix[i][b-1-flag]);
            }

            for(int j =b-1-flag;j>=flag;j--){
                res.add(matrix[end][j]);
            }

            for(int i=end-1;i>start;i--){
                res.add(matrix[i][flag]);
            }
            if(end-start==2){
                for(int j=flag+1;j<b-1-flag;j++){
                    res.add(matrix[end-1][j]);
                }
                return res;
            }
            if(b-1-flag ==flag+1){
                return res;
            }
            if(b-1-flag ==flag+2){
                for(int i =start+1;i<=end-1;i++){
                    res.add(matrix[i][flag+1]);
                }
                return res;
            }
            start++;
            end--;
            flag++;
        }
        return res;

    }

15.包含min函数的栈:

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

-思路:任务完成栈的数据结构同时要加一个min函数,完成出栈、入栈、获取栈顶元素。
规定了时间复杂度,肯定要牺牲空间。加入min辅助栈,出栈的时候两个都出;入栈的时候,min栈需要比较大小;最小值总是在min的top

static Stack<Integer> data = new Stack<Integer>();
    static Stack<Integer> min = new Stack<Integer>();

    public static void push(int node) {
        data.push(node);
        if(min.isEmpty() || node<min.peek()){
            min.push(node);
        }else{
            min.push(min.peek());
        }
    }

    public static void pop() {
        min.pop();
        data.pop();
    }

    public static int top() {
        return data.peek();
    }

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

16、栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

  • 麻烦思路:新建一个堆栈,模拟入栈的情况,根据出栈的判断是否正确,逻辑上比较繁琐。1、判断是否相等且是否还能入栈‘’‘’‘’‘’‘
public boolean IsPopOrder(int [] pushA,int [] popA) {
        int len = pushA.length;
        Stack<Integer> data = new Stack<Integer>();
        int i=0,j=0;
        while(j<len){

            if (i<len && pushA[i] == popA[j]) {
                i++;
                j++;
            }else if (data.isEmpty()){//入栈不等于出栈
                data.push(pushA[i]);
                i++;
            }else if(data.peek() == popA[j]){
                    data.pop();
                    j++;
            }else if(i >= len-1){
                return false;
            }else{ 
                data.push(pushA[i]);
                i++;
            }

        }
        return true;

    }
  • 简单思路:没有必要对push进行验证,直接放进辅助栈即可,没放进去,都要进行循环比较辅助栈与pop的关系,这样清楚明了
public static boolean IsPopOrder1(int [] pushA,int [] popA){
        int len = pushA.length;
        Stack<Integer> data = new Stack<Integer>();
        for(int i=0,j=0;i<len;i++){
            data.push(pushA[i]);
            while(!data.isEmpty() && data.peek() == popA[j]){
                data.pop();
                j++;
            }
        }
        return data.isEmpty();
    }

17、复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

  • 思路:由于题目说不可以直接引用参数中的结点,所以不能直接遍历赋值;
  • 分三步:1、将原有链表,每个节点后面加上相同元素,同时next值也复制;2、遍历复制后的,将random值也进行复制;3、遍历取出复制的部分
public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null){
            return null;
        }
        RandomListNode res = pHead;
        while(res != null){
            RandomListNode clone = new RandomListNode(res.label);
            clone.next = res.next;
            res.next = clone;
            res = res.next.next;

        }
        res = pHead;
        while(res!=null){
            res.next.random = res.random == null? null:res.random.next;
            res = res.next.next;
        }
        res = pHead;
        RandomListNode c = pHead.next;
        //就是把遍历每个,同时将他的next变为next.next
        while(res.next != null){

            RandomListNode cloneNode = res.next;
            res.next = res.next.next;
            res = cloneNode;

        }
        return c;
    }

18、重建二叉树(2019.2.24)

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

  • 思路:根据先序找到根节点,在中序中可知道左子树,右子树范围;再递归查找左子树的右子树的。
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        return sort(pre,in,0,0,pre.length);
    }
    private static TreeNode sort(int[] pre,int[] in,int i,int j,int count){
           if(count>0){
               int a=0;
               for(;a<count;a++){
                   if(pre[i] == in[a+j])
                       break;
               }
               TreeNode root = new TreeNode(pre[i]);
               root.left = sort(pre,in,i+1,j,a);
               root.right = sort(pre,in,i+a+1,j+a+1,count-a-1);
               return root;
           }
            return null;
        }
    
}

19、树的子结构(类以与二叉查找树的后序遍历)

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

  • 思路:首先题意,如何判断是否为子结构,那么就对A遍历,找到值等于B的根节点(函数1),递归比较左右子树。
  • 找到相等的,再去递归找B的left和right(函数2) ,其中对于空结点的判断是核心。
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean result = false;
        if(root1 != null &&root2 != null){
            if(root1.val == root2.val){
                result = isSubtree(root1,root2);
            }
            if(!result){
                result = HasSubtree(root1.left,root2);
            }
            if(!result){
                result = HasSubtree(root1.right,root2);
            }
        }
        return result;
    }

    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);

    }

20、二叉树的镜像

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

  • 思路:递归。就是一直遍历从根节点开始将每个结点左右交换,遍历完就是镜像。不需要找到根节点之后再遍历。
 public static void Mirror(TreeNode root) {
        TreeNode temp = null;
        if(root!=null){
            temp = root.left;
            root.left = root.right;
            root.right = temp;
            if(root.left != null)
                Mirror(root.left);
            if(root.right !=null)
                Mirror(root.right);
        }
    }

21、从上往下打印二叉树

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

  • 思路:层次遍历,也是BFS,借助递归不好实现,转变思路,借用队列分容易实现。
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        ArrayList<TreeNode> queue = new ArrayList<TreeNode>();
        if(root == null){
            return res;
        }
        queue.add(root);
        while(!queue.isEmpty()){
            TreeNode temp = queue.remove(0);
            if(temp.left!=null)
                queue.add(temp.left);
            if(temp.right!=null)
                queue.add(temp.right);
            res.add(temp.val);

        }
        return res;
    }

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

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

  • 思路:通过举例发现规律,根节点既末尾应该是前面值的分界线,可以通过这个判断,递归进行

❌错误仅仅考虑了根节点与左右子树关系,没有考虑左右子树是否是二叉查找树

public boolean VerifySquenceOfBST(int [] sequence) {
        int len = sequence.length;
        if(len<1){
            return true;
        }
        int G = sequence[len-1],i=0;
        for(;i<len-1;i++){
            if(sequence[i]>G){
                break;
            }
        }
        int j =i;
        for(;j<len-1;j++){
            if(sequence[j]<G){
                break;
            }
        }
        if(j == len-1){
            return true;
        }else{
            return false;
        }
    }

正确✔(使用递归)

public boolean VerifySquenceOfBST(int [] sequence) {
        int len = sequence.length;
        if(len<1){
            return false;
        }
        return BST(sequence,0,len-1);
    }
    public boolean BST(int [] sequence,int x,int y){
        if(y-x>1){
            int i=x,j;
            for(;i<y;i++){
                if(sequence[i]>sequence[y]){
                    break;
                }
            }
            j=i;
            for(;j<y;j++){
                if(sequence[j]<sequence[y]){
                    return false;
                }
            }
            return BST(sequence,x,i-1) && BST(sequence,i,j-1);
        }
        return true;
    }

23、二叉树中和为某一值的路径

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

  • 思路:往下找直到根节点的和为目标值,相等且是根节点就加入res中,否则再去递归查找左子树右子树,最后还要删除该节点。
ArrayList<Integer> R = new ArrayList<Integer>();
    ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();

    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        if(root == null){
            return res;
        }
        find(root,target);
        return res;
    }
    public void find(TreeNode root, int target){
        
        R.add(root.val);
        if(root.val == target && root.left == null && root.right == null){
            res.add(new ArrayList<>(R));
        }
        if(root.left!=null){
            find(root.left,target-root.val);
        }
        if(root.right!=null){
            find(root.right,target-root.val);
        }
        R.remove(R.size()-1);
    }

24、二叉搜索树与双向链表

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

  • 思路:根据中序遍历进行变化,使用堆栈,先一直找左子树入栈,直到根节点。然后开始出栈,同时把右子树入栈。对于这道题就是需要在出栈时进行操作,出栈的数左指向前一个出栈的,前一个出栈右指该数,所以要知道前一个结点。
public TreeNode Convert(TreeNode root){
        TreeNode first = null;
        if(root!=null){
            Stack<TreeNode> temp = new Stack<TreeNode>();
            TreeNode pre = null;
            boolean flag = true;
            while(!temp.isEmpty() || root!=null){
                if(root!=null){
                    temp.push(root);
                    root = root.left;
                }else{
                    root = temp.pop();
                    if(flag){
                        first = root;
                        pre = root;
                        flag = false;
                    }else{
                        pre.right = root;
                        root.left = pre;
                        pre = root;
                    }
                    root = root.right;
                }
            }
        }
        return first;
    }
  • 使用递归法:同样也是用中序的递归,关键点在于获取链表的头结点,通过一个first变量。另外在对根节点操作的部分,需要改进,就是要让前面排好序的right指向现在的,现在left的指向以前的。
TreeNode head = null;
    TreeNode first = null;
    public TreeNode Convert1(TreeNode root){
        sub(root);
        return first;
    }
    public void sub(TreeNode root){
        if(root == null) return;
        sub(root.left);
        if(head == null){
            first = root;
            head = root;
        }else{
            root.left = head;
            head.right = root;
            head = root;
        }
        sub(root.right);
    }

25、数组中出现次数超过一半的数字

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

  • 思路:先排序,再查找时间复杂度高。选择O(n)的算法。
  1. 通过快排的方法找到中位数,如果出现次数大于等于一半,肯定是中位数,再去判断是不是。
public static int MoreThanHalfNum_Solution(int[] array) {
        int temp,len = array.length,start=0,end =len-1;
        temp = getmid(array,start,end);
        while (temp!=len/2){
            if(temp>len/2){
                end = temp-1;
                temp = getmid(array,start,end);
            }else{
                start = temp+1;
                temp = getmid(array,temp+1,end);
            }
        }
        if(check(array,array[temp]))
            return array[temp];
        else
            return 0;
    }

    //
    public static boolean check(int [] array,int num){
        int len = array.length,time=0;
        for(int i=0;i<len;i++){
            if(num == array[i])
                time++;
        }
        if(time>len/2)
            return true;
        else
            return false;
    }
    public static int getmid(int [] num,int i,int j){
        int temp = num[i];
        while(i<j){
            while (i<j && temp<=num[j]){
                j--;
            }
            if(i<j){
                num[i] = num[j];
            }
            while (i<j && temp >= num[i]){
                i++;
            }
            if(i<j){
                num[j] = num[i];
            }
        }
        num[i] = temp;
        return i;
    }
  1. 通过数组的特点,最简单方法,一个数字数组中出现次数大于len/2,则该数出现的次数一定是最多的,所以可以直接用两个变量来遍历完数组,找到可能是该数的数字。再去判断这个数是不是
//方法2
    public static int MoreThanHalfNum_Solution1(int[] array) {
        int len = array.length;
        int temp=array[0],count=1,i,j;
        for(i=1;i<len;i++){
            if(array[i-1] == array[i]){
                count++;
                temp = array[i];
            }else if(count == 1){
                temp = array[i];
            }else{
                count--;
            }
        }
        if(check(array,temp))
            return temp;
        else
            return 0;
    }

    //
    public static boolean check(int [] array,int num){
        int len = array.length,time=0;
        for(int i=0;i<len;i++){
            if(num == array[i])
                time++;
        }
        if(time>len/2)
            return true;
        else
            return false;
    }
  • 变形:找出大于1/3个数的,就是找出现次数最多的前两个,对于1/n的一样,找出次数最多的n-1个。只需要遍历一次即可
//取出数组中不连续的值,达到1/3len个(排序之后)
    public static ArrayList san(int [] a){
        int len = a.length;
        ArrayList list = new ArrayList();
        int x=0,y=0;
        int xnum=0,ynum=0;
        for(int i =0;i<len;i++){
            if(xnum == 0||x==a[i]){
                x = a[i];
                ++xnum;
            }else if(ynum == 0||y==a[i]){
                y = a[i];
                ++ynum;
            }else{
                xnum--;
                ynum--;
            }
        }
        if(check(x,a)){
            list.add(x);
        }
        if(check(y,a)){
            list.add(y);
        }
        return list;

    }

    public static boolean check(int x,int[] a) {
        int num =0;
        for(int i=0;i<a.length;i++){
            if(a[i] == x){
                num++;
            }
        }
        if(num>a.length/3)
            return true;
        else
            return false;
    }

//连续的更简单,就是遍历一次只用一个变量即可
    public static ArrayList san1(int [] a){
        int len = a.length;
        ArrayList list = new ArrayList();
        int x=0;
        int xnum=0;
        for(int i =0;i<len;i++){
            if(xnum == 0||x==a[i]){
                x = a[i];
                ++xnum;
            }else{
                if(xnum>len/3){
                    list.add(x);
                    xnum=1;
                    x=a[i];
                }else{
                    xnum=1;
                    x=a[i];
                }
            }
        }
        return list;
    }

26、最小的K个数

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

  • 思路1:与用快排思路相似,找到位置为K-1的数字,同时前边都是小于他的,后边都是大于他的。(但是改变了原数组中元素位置)
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        int len = input.length;
        int start =0,end = len-1;
        int temp = getmid(input,start,end);
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(len<k || k<=0 || len<=0){
            return res;
        }
        while(temp!=k-1){
            if(temp>k-1){
                end = temp-1;
                temp = getmid(input,start,end);
            }else{
                start = temp+1;
                temp = getmid(input,start,end);
            }
        }
        for(int i=0;i<=temp;i++){
            res.add(input[i]);
        }
        return res;
    }

    //核心算法,也是快排的重要
    public int getmid(int [] array,int i,int j){
        int temp = array[i];
        while(i<j){
            if(i<j && temp<=array[j]){
                j--;
            }
            if(i<j){
                array[i] = array[j];
            }
            if(i<j&&temp>=array[i]){
                i++;
            }
            if(i<j){
                array[j] = array[i];
            }
        }
        array[i] = temp;
        return i;
    }
  • 思路2(适合处理海量数据)堆排序:堆排序取出最大值O(1),插入删除是O(logn)。每次比较堆排序的第一个,如果比他小,放进去,重新排序。最后得到的就是K个最小的。同理找K个最大的话,建立最小堆,比每次比较第一个,如果比他大则放进去。
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        int len = input.length;
        if(k<1||len<k){
            return res;
        }
        int [] copy = new int[k];
        for(int i=0;i<k;i++){
            copy[i] = input[i];
        }

        for(int x=k/2-1;x>=0;x--){
            sift(copy,x,k);
        }

        for(int i=k;i<input.length;i++){
            if(copy[0]>input[i]){
                copy[0] = input[i];
                sift(copy,0,k);
            }
        }
        
        for(int i=0;i<k;i++){
            res.add(copy[i]);
        }
        return res;

    }
    //构成最大堆
    public  static void sift (int[] input,int low,int high){
        int parent = low;
        int temp = input[parent];
        int child = 2*parent+1;
        while(child<high){
            if(child<high-1 && input[child]<input[child+1]){
                child++;
            }
            if(temp<input[child]){
                input[parent] = input[child];
                parent = child;
                child = 2*parent+1;
            }else{
                break;
            }

        }
        input[parent] = temp;
    }

27、字符串的排列

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

  • 全排列题目
  • 思路1:递归法;回溯法;就是第一个数和后面所有数交换位置,然后再第二个数,一直递归。要注意遇到相同的直接跳过。举例就是{0与1交换,然后递归对2开始的后面数字做处理,最后把0与1再交换回来},这是i++后的一次过程,交换回来才能对0跟2进行交换,否则可能是1和2交换。
	public static ArrayList<String> Permutation(String str) {
        char [] s = str.toCharArray();
        ArrayList<String> ret = new ArrayList<String>();
        if(s.length>0 && str!=null)
            fullsort(s,0,ret);
        Collections.sort(ret);
        return ret;
    }

    public static void fullsort(char[] s,int begin,ArrayList<String> ret){
        if(begin == s.length-1){
            ret.add(String.valueOf(s));
        }else{
            Set<Character> chasrset = new HashSet<Character>();
            for(int i = begin;i<s.length;i++){
                if(i==begin || !chasrset.contains(s[i])){
                    chasrset.add(s[i]);
                    swap(s,i,begin);
                    fullsort(s,begin+1,ret);
                    swap(s,begin,i);
                }
            }
        }
    }

    public static void swap(char [] s,int i,int j){
        char temp = s[i];
        s[i] = s[j];
        s[j] = temp;
    }
  • 思路2:非递归(字典序法)字典序就是按照字典的顺序,可以很容易比较两个数的大小,就是从头往后比较每一位的大小

一般而言,设P是[1,n]的一个全排列。
      P=P1P2…Pn=P1P2…Pj-1PjPj+1…Pk-1PkPk+1…Pn
    find:  j=max{i|Pi          k=max{i|Pi>Pj}
      1, 对换Pj,Pk,
      2, 将Pj+1…Pk-1PjPk+1…Pn翻转
P’= P1P2…Pj-1PkPn…Pk+1PjPk-1…Pj+1即P的下一个

public ArrayList<String> Permutation2(String str){
        ArrayList<String> list = new ArrayList<String>();
        if(str==null || str.length() == 0){
            return list;
        }
        char [] temp = str.toCharArray();
        Arrays.sort(temp);
        list.add(String.valueOf(temp));
        int len = temp.length;
        while(true){
            int lIndex = len-1;
            int rIndex = 0;
            while(lIndex>=1&& temp[lIndex-1]>=temp[lIndex]){
                lIndex--;
            }
            if(lIndex == 0)
                break;
            rIndex = lIndex;
            while(rIndex<len && temp[rIndex]>temp[rIndex-1])
                rIndex++;
            swap(temp,lIndex-1,rIndex-1);
            reverse(temp,lIndex);
            list.add(String.valueOf(temp));
        }
        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);
            } 
        }
    }

30、连续子数组的最大和

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

  • 思路:不用考虑当前值,只用考虑前面的值的正负,如果是正的可以直接加当前值,如果是负的,直接把当前值赋给最大值。每次都要比较当前值与最大值的大小。
public int FindGreatestSumOfSubArray(int[] array) {
        int max=array[0],len=array.length,ret=array[0];
        if(len == 0 || array == null)
            return 0;

        for(int i=1;i<len;i++){
            if(max>=0){
                max += array[i];
            }else{
                max = array[i];
            }

            if(max>ret){
                ret = max;
            }
        }
        return ret;
    }
  • 思路2 动态规划:每一次前进要不是把这个值放入max里面,要不就是前面的都不要了。因此每次都是最大的话,肯定就能得到最大的值,同时每次还会记录当前结果的最大值
public int FindGreatestSumOfSubArray1(int[] array) {
        int max=array[0],len=array.length,ret=array[0];
        if(len == 0 || array == null)
            return 0;
        for(int i=1;i<len;i++){
            max = Math.max(max+array[i],array[i]);
            ret = Math.max(ret,max);
        }
        return ret;
    }

你可能感兴趣的:(Coding)