劍指offer(java語言的答案)

轉載網址:http://blog.csdn.net/sinat_29912455/article/details/51137349

3、二維數組中的查找

題目描述 
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15

思路: 從右上角開始,若小,向下走,刪除一行,若大,向左走,刪除一列

/*
利用二維數組由上到下,由左到右遞增的規律,
那麼選取右上角或者左下角的元素a[row] [col]與target進行比較,
當target小於元素a[row] [col]時,那麼target必定在元素a所在行的左邊,
即col--;
當target大於元素a[row][col]時,那麼target必定在元素a所在列的下邊,
即row++;
*/
public class Solution {
    public boolean Find(int [][] array,int target) {
        int row=0;
        int col=array[0].length-1;
        while(row<=array.length-1&&col>=0){
            if(target==array[row][col])
                return true;
            else if(target>array[row][col])
                row++;
            else
                col--;
        }
        return false; 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

4、替換空格

題目描述 
請實現一個函數,將一個字符串中的空格替換成“%20”。例如,當字符串爲We Are Happy.則經過替換之後的字符串爲We%20Are%20Happy。

思路:先遍歷一遍字符,統計空格數,由此計算替換之後的總長度。然後從後往前加載字符串,如果發現空格,就替換20%

/*
問題1:替換字符串,是在原來的字符串上做替換,還是新開闢一個字符串做替換!
問題2:在當前字符串替換,怎麼替換才更有效率(不考慮java裏現有的replace方法)。
      從前往後替換,後面的字符要不斷往後移動,要多次移動,所以效率低下
      從後往前,先計算需要多少空間,然後從後往前移動,則每個字符只爲移動一次,這樣效率更高一點。
*/
public class Solution {
    public String replaceSpace(StringBuffer str) {
        int spacenum = 0;//spacenum爲計算空格數
        for(int i=0;i<str.length();i++){
            if(str.charAt(i)==' ')
                spacenum++;
        }
        int indexold = str.length()-1; //indexold爲爲替換前的str下標
        int newlength = str.length() + spacenum*2;//計算空格轉換成%20之後的str長度
        int indexnew = newlength-1;//indexold爲爲把空格替換爲%20後的str下標
        str.setLength(newlength);//使str的長度擴大到轉換成%20之後的長度,防止下標越界
        for(;indexold>=0 && indexold<newlength;--indexold){ 
                if(str.charAt(indexold) == ' '){  //
                str.setCharAt(indexnew--, '0');
                str.setCharAt(indexnew--, '2');
                str.setCharAt(indexnew--, '%');
                }else{
                    str.setCharAt(indexnew--, str.charAt(indexold));
                }
        }
        return str.toString();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

5、從尾到頭打印鏈表

題目描述: 
輸入一個鏈表,從尾到頭打印鏈表每個節點的值。

思路1:棧

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*/
import java.util.Stack;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode == null){
            ArrayList list = new ArrayList();
            return list;
        }
        Stack<Integer> stk = new Stack<Integer>();
        while(listNode != null){
            stk.push(listNode.val);
            listNode = listNode.next;
        }
        ArrayList<Integer> arr = new ArrayList<Integer>();
        while(!stk.isEmpty()){
            arr.add(stk.pop());
        }
        return arr;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

思路2:遞歸

public class Solution {
    public void printListFromTailToHead(ListNode listNode) {
      if(listNode != null){
            if(listNode.next != null){
                printListFromTailToHead(listNode.next);
            }
           System.out.print(""+listNode.var);
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

6、重建二叉樹

題目描述:已知中序和前序,請重建二叉樹

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        TreeNode root=reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        return root;
    }
    //前序遍歷{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6}
    private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn) {  
        if(startPre>endPre||startIn>endIn)
            return null;
        TreeNode root=new TreeNode(pre[startPre]);
        for(int i=startIn;i<=endIn;i++)
            if(in[i]==pre[startPre]){
                root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);  //注意pre的位置,要用偏移量,不能用i,因爲i是在變化              
                root.right=reConstructBinaryTree(pre,i-startIn+startPre+1,endPre,in,i+1,endIn);
            }                 
        return root;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

引申:已知中序和後序求前序

package com.zhuang.tree;

public class Main {

     public static class TreeNode {
          int val;
          TreeNode left;
          TreeNode right;
          TreeNode(int x) { val = x; }
      }

     public static TreeNode reConstructBinaryTree(int [] post,int [] in) {
            TreeNode root=reConstructBinaryTree(post,0,post.length-1,in,0,in.length-1);
            return root;
        }


        private static TreeNode reConstructBinaryTree(int [] post,int startPost,int endPost,int [] in,int startIn,int endIn) {

            if(startPost>endPost||startIn>endIn)
                return null;

            TreeNode root=new TreeNode(post[endPost]);

            for(int i=startIn;i<=endIn;i++)
                if(in[i]==post[endPost]){
                    root.left=reConstructBinaryTree(post,startPost,startPost+i-startIn-1,in,startIn,i-1);
                    root.right=reConstructBinaryTree(post,startPost+i-startIn,endPost-1,in,i+1,endIn);
                }

            return root;
        }

        public static void preOrder(TreeNode root){
            if(root == null){
                return;
            }
            System.out.println(root.val);
            preOrder(root.left);
            preOrder(root.right);
        }

        public static void main(String[] args){
            int[] post = {2,4,3,1,6,7,5};
            int[] in = {1,2,3,4,5,6,7};
            TreeNode root = reConstructBinaryTree(post, in);
            preOrder(root);
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

7、用兩個棧實現隊列

題目描述 
用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素爲int類型。

思路:入棧給stack1,出棧時,若stack2不爲空,則出棧,若爲空,把stack1的內容全都放入stack2,然後再出棧

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

   public void push(int node) {
        stack1.push(node);
    }


   public int pop() {

       while(!stack2.isEmpty())
        {
            return stack2.pop();
        }

        while(!stack1.isEmpty())
        {
            stack2.push(stack1.pop());
        }

        return stack2.pop();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

8、旋轉數組的最小數字

題目描述 
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 
輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。 
例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 
NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。

import java.util.ArrayList;

public class Solution {
    /*
     * 傳進去旋轉數組,注意旋轉數組的特性: 1.包含兩個有序序列 2.最小數一定位於第二個序列的開頭 3.前序列的值都>=後序列的值
     */

    // 用到了快速排序的快速定位範圍的思想,
    public int minNumberInRotateArray(int[] array) {

        if (array == null || array.length == 0) {
            return 0;
        }
        int low = 0;//指向第一個
        int up = array.length - 1;//指向最後一個
        int mid = low;

        // 當low和up兩個指針相鄰時候,就找到了最小值,也就是
        // 右邊序列的第一個值

        while (array[low] >= array[up]) {
            if (up - low == 1) {
                mid = up;
                break;
            }
            // 如果low、up、mid下標所指的值恰巧相等
            // 如:{0,1,1,1,1}的旋轉數組{1,1,1,0,1}
            if (array[low] == array[up] && array[mid] == array[low])
                return MinInOrder(array);
            mid = (low + up) / 2;
            // 這種情況,array[mid]仍然在左邊序列中
            if (array[mid] >= array[low])
                low = mid;// 注意,不能寫成low=mid+1;
            // 要是這種情況,array[mid]仍然在右邊序列中
            else if (array[mid] <= array[up])
                up = mid;
        }

        return array[mid];

    }

    private int MinInOrder(int[] array) {
        // TODO Auto-generated method stub
        int min = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] < min) {
                min = array[i];

            }
        }
        return min;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

9、斐波那契數列

題目描述 
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項。

思路1:遞歸,簡潔但效率不高

public int Fibonacci(int n) {        
        if(n<=0)
            return 0;
        if(n==1)
            return 1;
        return Fibonacci(n-1) + Fibonacci(n-2); 
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

思路2:循環,O(N)

public class Solution {
    public int Fibonacci(int n) {
        int preNum=1;
        int prePreNum=0;
        int result=0;
        if(n==0)
            return 0;
        if(n==1)
            return 1;
        for(int i=2;i<=n;i++){
            result=preNum+prePreNum;
            prePreNum=preNum;
            preNum=result;
        }
        return result;

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

擴展1:跳臺階

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

思路:斐波拉契數序列,初始條件n=1:只能一種方法,n=2:兩種 
對於第n個臺階來說,只能從n-1或者n-2的臺階跳上來,所以 
F(n) = F(n-1) + F(n-2)

擴展2:變態跳臺階

題目描述 
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

思路: 
因爲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(n-1)=f(n-2)+f(n-3)+…+f(1) 
所以f(n)=2*f(n-1) 
所以f(n)=2的(n-1)次冪

public class Solution {
    public int JumpFloorII(int target) {
        if(target<=0)
            return 0;
        return 1<<(target-1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

10、二進制中1的個數

題目描述 
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

最差的解法:因爲要考慮負數的問題,若是負數,因爲要保證一直輸負數,多以第一位一直爲1,最後會變成0XFFFFFFFF,造成死循環

public class Solution {
public int  NumberOf1(int n) {
        int count= 0;
        int flag = 1;
        while (n!= 0){
            if ((n & 1) != 0){
                count++;      
            }
            n = n>>1;
        }
         return count;
     }    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

改進的解法:

public class Solution {
public int  NumberOf1(int n) {
        int count= 0;
        int flag = 1;
        while (flag != 0){
            if ((n & flag) != 0){
                count++;      
            }
            flag  = flag << 1;
        }
         return count;
     }    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

最精妙的解法:

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n!= 0){
            count++;
            n = n & (n - 1);//每一次將最後一個1變成0
         }
        return count;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

11、數值的整數次方

題目描述 
給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。

思路1:本題主要考慮邊界問題,全面不夠高效的解法,注意:由於計算機表示小數(包括float和double型小數)都有誤差,我們不能直接用==判斷兩個小數是否相等,如果兩個小數的差的絕對值很小,比如小於0.0000001,就可以認爲他們相等

public class Solution {
    public double Power(double base, int exponent) {
        double res = 0.0;
        if (equal(base, 0.0) && exponent < 0) {
            throw new RuntimeException("0的負數次冪沒有意義");
        }
        // 這裏定義0的0次方爲1
        if (exponent == 0) {
            return 1.0;
        }
        if (exponent < 0) {
            res = powerWithExponent(1.0/base, -exponent);
        } else {
            res = powerWithExponent(base, exponent);
        }
        return res;
    }

    private double powerWithExponent(double base, int exponent) {
        double res = 1.0;
        for (int i = 1; i <= exponent; i++) {
            res = res * base;
        }
        return res;
    }

    // 判斷double類型的數據
    private boolean equal(double num1, double num2) {
        if (Math.abs(num1 - num2) < 0.0000001) {
            return true;
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

思路2:n爲偶數時:a^n=a^n/2 * a^n/2; 
n爲奇數,a^n=(a^(n-1)/2)* (a^(n-1/2))* a 
所以對乘法處進行優化,如果是32次方,等於16次方*16次方

 /*
    ** 對乘法進行優化的部分
    */
    private double powerWithExponent(double base, int exponent) {
        if(exponent==0){
            return 1;
        }

        if(exponent==1){
            return base;
        }

       double result = powerWithExponent(base,exponent>>1);//每次除以2
       result*=result;//最後相乘,如果是奇數,還要乘一個

        //如果是奇數次方的情況,最終除2餘1要與base相乘
        if((exponent & 0x1)==1){
            result *= base;
        }
        return result;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

12、打印1到最大的n位數

題目描述:如n=3,則從1打印到999

public class Solution {

    // ====================方法一====================
    public static void Print1ToMaxOfNDigits(int n) {
        if (n <= 0)
            return;

        char[] number = new char[n];

        //每一個字符設爲0
        for (int i = 0; i < n; i++) {
            number[i] = '0';
        }

        while (!Increment(number)) {//如果加法溢出,則退出,否則打印數字
            PrintNumber(number);
        }

    }

    // 字符串number表示一個數字,在 number上增加1
    // 如果做加法溢出,則返回true;否則爲false
    public static boolean Increment(char[] number) {
        boolean isOverflow = false;//溢出標誌
        int nTakeOver = 0;//進位
        int nLength = number.length;

        for (int i = nLength - 1; i >= 0; i--) {//從後向前,最後一位數字加1
            int nSum = number[i] - '0' + nTakeOver;
            if (i == nLength - 1)
                nSum++;

            if (nSum >= 10) {
                if (i == 0)
                    isOverflow = true;
                else {
                    nSum -= 10;
                    nTakeOver = 1;
                    number[i] = (char) ('0' + nSum);
                }
            } else {
                number[i] = (char) ('0' + nSum);
                break;
            }
        }

        return isOverflow;
    }

    // 字符串number表示一個數字,數字有若干個0開頭
    // 打印出這個數字,並忽略開頭的0
    public static void PrintNumber(char[] number) {
        boolean isBeginning0 = true;
        int nLength = number.length;

        //標誌位的思想,從第一位不爲0的數字開始打印,如000123,打印123
        for (int i = 0; i < nLength; ++i) {
            if (isBeginning0 && number[i] != '0')
                isBeginning0 = false;

            if (!isBeginning0) {
                System.out.print(number[i]);
            }
        }
        System.out.println();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

思路2:用遞歸,代碼簡潔,思路不好想,每一位都是從0到9的全排列

public class Solution {

    // // ====================方法二:遞歸====================
    public static void Print1ToMaxOfNDigits(int n) {
        if (n <= 0)
            return;

        char[] number = new char[n];

        for (int i = 0; i < 10; ++i) {
            number[0] = (char) (i + '0');
            Print1ToMaxOfNDigitsRecursively(number, n, 0);
        }

    }

    public static void Print1ToMaxOfNDigitsRecursively(char[] number, int length,int index) {
        if (index == length - 1) {
            PrintNumber(number);
            return;
        }

        for (int i = 0; i < 10; ++i) {
            number[index + 1] = (char) (i + '0');
            Print1ToMaxOfNDigitsRecursively(number, length, index + 1);
        }
    }

    // 字符串number表示一個數字,數字有若干個0開頭
    // 打印出這個數字,並忽略開頭的0
    public static void PrintNumber(char[] number) {
        boolean isBeginning0 = true;
        int nLength = number.length;

        // 標誌位的思想,從第一位不爲0的數字開始打印,如000123,打印123
        for (int i = 0; i < nLength; ++i) {
            if (isBeginning0 && number[i] != '0')
                isBeginning0 = false;

            if (!isBeginning0) {
                System.out.print(number[i]);
            }
        }
        System.out.println();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

13、在O(1)時間刪除鏈表結點

給定單向鏈表頭指針和一個節點指針,在O(1)時間刪除鏈表結點

/*
    對於刪除節點,我們普通的思路就是讓該結點的前一個節點指向改節點的下一個節點
    */
    public void delete(Node head, Node toDelete){
        if(toDelete == null){
            return ;
        }
        if(toDelete.next != null){//刪除的節點不是尾節點
            toDelete.val = toDelete.next.val;
            toDelete.next = toDelete.next.next;
        }else if(head == toDelete){//鏈表只有一個節點,刪除頭結點也是尾節點
            head = null;
        }else{ //刪除的節點是尾節點的情況
            Node node = head;
            while(node.next != toDelete){//找到倒數第二個節點
                node = node.next;
            }
            node.next = null;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

14、調整數組順序使奇數位於偶數前面

題目描述 
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

public class Solution {
    public void reOrderArray(int [] array) {
        //註釋的部分使用快速排序的算法,很明顯快速排序是不穩定的,這裏需要用歸併排序
        /*
        if(array.length == 0){
            return;
        }
        int high = array.length - 1;
        int low = 0;
        while(low < high){
            while(low < high && array[low] % 2 == 1){
                low ++;
            }
            while(low < high && array[high] % 2 == 0){
                high --;
            }
            int temp = array[low];
            array[low] = array[high];
            array[high] = temp;
        }*/

        //用用歸併排序的思想,因爲歸併排序是穩定的
        int length = array.length;
        if(length == 0){
            return;
        }
        int[] des = new int[length];
        MergeMethod(array, des, 0,length - 1);
    }
    public void MergeMethod(int[] array, int [] des, int start, int end){
        if(start < end){
            int mid = (start + end) / 2;
            MergeMethod(array, des, start, mid);
            MergeMethod(array, des, mid + 1, end);
            Merge(array, des, start, mid, end);
        }
    }

    public void Merge(int[] array, int[] des, int start, int mid, int end){
        int i = start;
        int j = mid + 1;
        int k = start;
        while(i <= mid && array[i] % 2 == 1){
            des[k++] = array[i++];
        }
        while(j <= end && array[j] % 2 == 1){
            des[k++] = array[j++];
        }
        while(i <= mid){
            des[k++] = array[i++];
        }
        while(j <= end){
            des[k++] = array[j++];
        }

        for(int m = start; m <= end; m++){
            array[m] = des[m];
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

15、鏈表中倒數第k個結點

題目描述 
輸入一個鏈表,輸出該鏈表中倒數第k個結點。

思路:兩個指針,先讓第一個指針和第二個指針都指向頭結點,然後再讓第一個指正走(k-1)步,到達第k個節點。然後兩個指針同時往後移動,當第一個結點到達末尾的時候,第二個結點所在位置就是倒數第k個節點了

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head==null||k<=0){
            return null;
        }
        ListNode pre=head;
        ListNode last=head;       
        for(int i=1;i<k;i++){
            if(pre.next!=null){
                pre=pre.next;
            }else{
                return null;
            }
        }
        while(pre.next!=null){
            pre = pre.next;
            last=last.next;
        }
        return last;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

16、反轉鏈表

題目描述 
輸入一個鏈表,反轉鏈表後,輸出鏈表的所有元素。

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

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

        ListNode pPre = null;
        ListNode p = head;
        ListNode pNext = head.next;
        ListNode newHead = null;

        while (p != null) {
            pNext = p.next;//一定要記錄下來後面的節點
            if (pNext == null)
                newHead = p;
            p.next = pPre;//這裏的方向已經轉變
            pPre = p;
            p = pNext;//將保存的後面的節點作爲下一次循環的p

        }
        return newHead;

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

17、合併兩個排序的鏈表

題目描述 
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。

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

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

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

非遞歸方法:

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1==null&&list2==null)
            return null;
        if(list1==null&&list2!=null)
            return list2;
        if(list1!=null&&list2==null)
            return list1;
        ListNode head = null;
        if(list1.val<list2.val){
            head = list1;
            list1 = list1.next;
        }
        else{
            head = list2;
            list2 = list2.next;
        }
        ListNode cur = head;
        cur.next=null;
        while(list1!=null&&list2!=null){
            if(list1.val<list2.val){
                cur.next = list1;
                list1 = list1.next;
            }
            else{
                cur.next = list2;
                list2 = list2.next;
            }
            cur = cur.next;
            cur.next = null;
        }
        if(list1==null&&list2!=null){
            cur.next =list2;
        }else if(list2==null&&list1!=null){
            cur.next = list1;
        }
        return head;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

18、樹的子結構

題目描述 
輸入兩顆二叉樹A,B,判斷B是不是A的子結構。

思路:首先遍歷A樹,找到A的根節點和B的根節點相同的點,找到之後再遍歷A和B的各個子節點是否相同

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

    public TreeNode(int val) {
        this.val = val;

    }

}*/
public class Solution {
   public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root2==null) return false;
        if(root1==null && root2!=null) return false;       
        boolean flag = false;
        if(root1.val==root2.val){
            flag = isSubTree(root1,root2);
        }
        if(!flag){
            flag = HasSubtree(root1.left, root2);
        }
        if(!flag){
            flag = HasSubtree(root1.right, root2);
        }
        return flag;
    }

    private boolean isSubTree(TreeNode root1, TreeNode root2) {
        if(root2==null) return true;
        if(root1==null && root2!=null) return false;       
        if(root1.val==root2.val){
            return isSubTree(root1.left, root2.left) && isSubTree(root1.right, root2.right);
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

19、二叉樹的鏡像

題目描述 
操作給定的二叉樹,將其變換爲源二叉樹的鏡像。 
二叉樹的鏡像定義:源二叉樹 

/ \ 
6 10 
/ \ / \ 
5 7 9 11 
鏡像二叉樹 

/ \ 
10 6 
/ \ / \ 
11 9 7 5

思路1:用棧結構(改成隊列結構也可以),將節點依次入棧,每個入棧的節點都鏡像他的子節點

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

    public TreeNode(int val) {
        this.val = val;
    }
}
*/

import java.util.Stack;

public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null){
            return;
        }
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.push(root);
        while(!stack.isEmpty()){
            TreeNode node = stack.pop();
            if(node.left != null||node.right != null){
                TreeNode temp = node.left;
                node.left = node.right;
                node.right = temp;
            }
            if(node.left!=null){
                stack.push(node.left);
            }
            if(node.right!=null){
                stack.push(node.right);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

思路2:前序遍歷的遞歸

public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null)  return;
        if(root.left != null || root.right != null)
        {   
        //創建臨時節點,交換左右節點       
            TreeNode tempNode = null;           
            tempNode = root.left;
            root.left = root.right;
            root.right = tempNode;
            Mirror(root.left);
            Mirror(root.right);

        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

20、順時針打印矩陣

題目描述 
輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字,例如,如果輸入如下矩陣: 
1 2 3 4 
5 6 7 8 
9 10 11 12 
13 14 15 16 
則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       ArrayList<Integer> list = new ArrayList<Integer>();
        int rows = matrix.length;
        int columns = matrix[0].length;

        if(matrix == null || columns <= 0 || rows <= 0){
            return null;
        }
        int start = 0;
        while(columns > start *2 && rows > start * 2){
            print1Circle(list,matrix,columns,rows,start);
            start++;
        }

        return list;
    }

    public void print1Circle(ArrayList<Integer> list, int[][] matrix,int columns, int rows, int start) {
        int endX = columns - 1 - start;
        int endY = rows - 1 - start;

        //從左往右打印一行
        for (int i = start; i <= endX; i++) {
            list.add(matrix[start][i]);
        }

        //從上往下打印一列,至少有兩行
        if (start < endY){
            for (int i = start+1; i <= endY; i++) {
                list.add(matrix[i][endX]);
            }
        }

         //從右往左打印一行,至少有兩行兩列
        if (start < endY && start < endX){
            for (int i = endX - 1; i >= start; i--) {
                list.add(matrix[endY][i]);
            }
        }

         //從下往上打印一列,至少有三行兩列
        if (start < endY -1 && start < endX){
            for (int i = endY - 1; i >= start + 1; i--) {
                list.add(matrix[i][start]);
            }
        }     

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

21、包含min函數的棧

題目描述 
定義棧的數據結構,請在該類型中實現一個能夠得到棧最小元素的min函數。

import java.util.Stack;

/*思路:用一個棧data保存數據,用另外一個輔助棧min保存依次入棧最小的數
比如,data中依次入棧,5,  4,  3, 8, 10, 11, 12, 1
       則min依次入棧,5,  4,  3,3,3, 3, 3, 1
每次入棧的時候,如果入棧的元素比min中的棧頂元素小或等於則入棧,否則不如棧。
*/ 
public class Solution {
    Stack data=new Stack();
    Stack min=new Stack();

    public void push(int node) {
        if(min.empty()){
            min.push(node);
        }else{
            int top=(int)min.peek();
            if(node<top){
               min.push(node);
            }else{
                min.push(top);
            }
        }
        data.push(node);
    }

    public void pop() {
        if(!(data.empty())){
            data.pop();
            min.pop();
        }
    }

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

    public int min() {
       if(min.empty()){
           return 0;
       }
       return (int)min.peek();    
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

22、棧的壓入、彈出序列

題目描述 
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。

【思路】借用一個輔助的棧,push序列依次入棧,每次都判斷,棧頂元素和pop序列是否相等,相等則彈出棧,不相等,則push序列繼續入棧,最後判斷棧是否爲空 
舉例: 
入棧1,2,3,4,5 
出棧4,5,3,2,1 
首先1入輔助棧,此時棧頂1≠4,繼續入棧2 
此時棧頂2≠4,繼續入棧3 
此時棧頂3≠4,繼續入棧4 
此時棧頂4=4,出棧4,彈出序列向後一位,此時爲5,,輔助棧裏面是1,2,3 
此時棧頂3≠5,繼續入棧5 
此時棧頂5=5,出棧5,彈出序列向後一位,此時爲3,,輔助棧裏面是1,2,3 
…. 
依次執行,最後輔助棧爲空。如果不爲空說明彈出序列不是該棧的彈出順序。

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length == 0 || popA.length == 0)
            return false;
        Stack<Integer> s = new Stack<Integer>();
        //用於標識彈出序列的位置
        int popIndex = 0;
        for(int i = 0; i< pushA.length;i++){
            s.push(pushA[i]);
            //如果棧不爲空,且棧頂元素等於彈出序列
            while(!s.empty() &&s.peek() == popA[popIndex]){
                //出棧
                s.pop();
                //彈出序列向後一位
                popIndex++;
            }
        }
        return s.empty();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

23、從上往下打印二叉樹——層次遍歷

題目描述 
從上往下打印出二叉樹的每個節點,同層節點從左至右打印。

思路:一個隊列容器,每次打印節點的時候把此節點的左右子節點加入進去

import java.util.*;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if(root==null){
            return list;
        }
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode treeNode = queue.poll();
            if (treeNode.left != null) {
                queue.offer(treeNode.left);
            }
            if (treeNode.right != null) {
                queue.offer(treeNode.right);
            }
            list.add(treeNode.val);
        }
        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

24、二叉搜索樹的後序遍歷序列

題目描述 
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。

public class Solution {
    public static boolean VerifySquenceOfBST(int[] sequence) {
        if(sequence.length ==0){
            return false;
        }
        return VerifySquenceOfBST1(sequence,0,sequence.length-1);
    }

    public static boolean VerifySquenceOfBST1(int[] sequence,int start,int end) {

        if(start > end)
            return true;
        int root=sequence[end];//後序遍歷最後一個節點爲根節點

       //在二叉搜索樹中左子樹節點小於根節點
        int i=0;
        for(;i<end;i++){
            if(sequence[i]>root){
                break;
            }
        }

        //在二叉搜索樹中右子樹節點大於根節點
        int j=i;
        for(;j<end;j++){
            if(sequence[j]<root)
                return false;
        }
        boolean left=true;
        boolean right=true;
        if(i>start){
            left=VerifySquenceOfBST1(sequence,start,i-1);
        }
        if(i<sequence.length-1)
            right=VerifySquenceOfBST1(sequence,i,end-1);
        return (left&&right);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

25、二叉樹中和爲某一值的路徑

題目描述 
輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。

import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/

public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        ArrayList<ArrayList<Integer>> pathList=new ArrayList<ArrayList<Integer>>();
        if(root==null)
            return pathList;
        Stack<Integer> stack=new Stack<Integer>();
        FindPath(root,target,stack,pathList );
        return pathList;

    }
    private void FindPath(TreeNode root, int target, Stack<Integer> path, ArrayList<ArrayList<Integer>> pathList) {
        if(root==null)
            return;
        //如果是葉子節點,判斷值是否是目標值
        if(root.left==null&&root.right==null){
            if(root.val==target){
                ArrayList<Integer> list=new ArrayList<Integer>();
                for(int i:path){
                    list.add(new Integer(i));
                }
                list.add(new Integer(root.val));
                pathList.add(list);
            }
        }
        else{//不是葉子節點就遍歷其子節點
            path.push(new Integer(root.val));
            //是按照前序遍歷的方式查找路徑,如果向上退出到父節點時,要回到target值,而不是target-root.val
            FindPath(root.left, target-root.val, path, pathList);
            FindPath(root.right, target-root.val, path,  pathList);
            path.pop();
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

26、複雜鏈表的複製

題目描述 
輸入一個複雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點)。

方法一:用hashMap映射原鏈表,犧牲O(N)空間換來時間

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
import java.util.HashMap; 
public class Solution {

    public RandomListNode Clone(RandomListNode pHead)

    {

        if(pHead == null) return null;

        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>();

        RandomListNode newHead = new RandomListNode(pHead.label);//複製鏈表的頭結點

        RandomListNode pre = pHead, newPre = newHead; 
        map.put(pre, newPre);

        //第一步,hashMap保存,原鏈表節點映射覆制鏈表節點
        while(pre.next != null){ 
            newPre.next = new RandomListNode(pre.next.label); 
            pre = pre.next; 
            newPre = newPre.next; 
            map.put(pre, newPre); 
        }

        //第二步:找到對應的random
        pre = pHead; 
        newPre = newHead;

        while(newPre != null){ 
            newPre.random = map.get(pre.random); 
            pre = pre.next; 
            newPre = newPre.next; 
        }

        return newHead; 
    } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

方法二:不借用輔助空間

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead){
        if(pHead==null)
            return null;
        RandomListNode pCur = pHead;
        //第一步:複製next 如原來是A->B->C 變成A->A'->B->B'->C->C'
        while(pCur!=null){
            RandomListNode node = new RandomListNode(pCur.label);
            node.next = pCur.next;
            pCur.next = node;
            pCur = node.next;
        }

        //第二步
        pCur = pHead;
        //複製random pCur是原來鏈表的結點 pCur.next是複製pCur的結點
        while(pCur!=null){
            if(pCur.random!=null)
                pCur.next.random = pCur.random.next;
            pCur = pCur.next.next;
        }

        //第三步
        RandomListNode head = pHead.next;//複製鏈表的頭結點
        RandomListNode cur = head;//偶數位置爲複製鏈表
        pCur = pHead;//奇數位置爲原鏈表
        //拆分鏈表
        while(pCur!=null){
            pCur.next = pCur.next.next;
            if(cur.next!=null)//注意最後一個複製節點的時候就沒有next的next
                cur.next = cur.next.next;
            cur = cur.next;
            pCur = pCur.next;
        }
        return head;       
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

27、二叉搜索樹與雙向鏈表

題目描述 
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。

方法一:遞歸中序遍歷

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

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//直接用中序遍歷
public class Solution {
    TreeNode head = null;
    TreeNode realHead = null;//雙向鏈表的頭結點
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return null;
        Convert(pRootOfTree.left);
        if (head == null) {
            head = pRootOfTree;
            realHead = pRootOfTree;
        } else {
            head.right = pRootOfTree;
            pRootOfTree.left = head;
            head = pRootOfTree;
        }
        Convert(pRootOfTree.right);
        return realHead;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

方法二:

/** 非遞歸 */
import java.util.Stack;
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return pRootOfTree;

        TreeNode list = null;
        Stack<TreeNode> s = new Stack<>();
        while(pRootOfTree != null || !s.isEmpty()){
            if(pRootOfTree != null) {
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree.right;
            } else {
                pRootOfTree = s.pop();
                if(list == null)
                    list = pRootOfTree;
                else {
                    list.left = pRootOfTree;
                    pRootOfTree.right = list;
                    list = pRootOfTree;
                }
                pRootOfTree = pRootOfTree.left;
            }
        }

        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

28、字符串的排列

題目描述 
輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。

擴展:求字符串的全組合

如:abc,全組合爲:a,b,c,ab,ac,bc,abc

public final class PermutationCombinationHolder {

    /** 1、數組元素的全組合 */
  public  static void combination(char[] chars) {
        char[] subchars = new char[chars.length]; //存儲子組合數據的數組
        //全組合問題就是所有元素(記爲n)中選1個元素的組合, 加上選2個元素的組合...加上選n個元素的組合的和
        for (int i = 0; i < chars.length; ++i) {
            final int m = i + 1;
            combination(chars, chars.length, m, subchars, m);
        }
    }

    /**
     *  n個元素選m個元素的組合問題的實現. 原理如下:
     *  從後往前選取, 選定位置i後, 再在前i-1個裏面選取m-1個.
     *  如: 1, 2, 3, 4, 5 中選取3個元素.
     *  1) 選取5後, 再在前4個裏面選取2個, 而前4個裏面選取2個又是一個子問題, 遞歸即可;
     *  2) 如果不包含5, 直接選定4, 那麼再在前3個裏面選取2個, 而前三個裏面選取2個又是一個子問題, 遞歸即可;
     *  3) 如果也不包含4, 直接選取3, 那麼再在前2個裏面選取2個, 剛好只有兩個.
     *  縱向看, 1與2與3剛好是一個for循環, 初值爲5, 終值爲m.
     *  橫向看, 該問題爲一個前i-1箇中選m-1的遞歸.
     */
    public static void combination(char[] chars, int n, int m, char[] subchars, int subn) {
        if (m == 0) { //出口
            for (int i = 0; i < subn; ++i) {
                System.out.print(subchars[i]);
            }
            System.out.println();
        } else {
            for (int i = n; i >= m; --i) { // 從後往前依次選定一個
                subchars[m - 1] = chars[i - 1]; // 選定一個後
                combination(chars, i - 1, m - 1, subchars, subn); // 從前i-1個裏面選取m-1個進行遞歸
            }
        }
    }
///////////////////////////////////////////////////////
///////////////////////////////////////////////////////


    /** 2、數組元素的全排列 */
   public static void permutation(char[] chars) {
        permutation(chars, 0, chars.length - 1);
    }

    /** 數組中從索引begin到索引end之間的子數組參與到全排列 */
   public static void permutation(char[] chars, int begin, int end) {
        if (begin == end) { //只剩最後一個字符時爲出口
            for (int i = 0; i < chars.length; ++i) {
                System.out.print(chars[i]);
            }
            System.out.println();
        } else {
            for (int i = begin; i <= end; ++i) { //每個字符依次固定到數組或子數組的第一個
                if (canSwap(chars, begin, i)) { //去重
                    swap(chars, begin, i); //交換
                    permutation(chars, begin + 1, end); //遞歸求子數組的全排列
                    swap(chars, begin, i); //還原
                }
            }
        }
    }

    public static void swap(char[] chars, int from, int to) {
        char temp = chars[from];
        chars[from] = chars[to];
        chars[to] = temp;
    }

    //判斷去重
  public static boolean canSwap(char[] chars, int begin, int end) {
        for (int i = begin; i < end; ++i) {
            if (chars[i] == chars[end]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        final char[] chars = new char[] {'a', 'b', 'c'};
        permutation(chars);
        System.out.println("===================");
        combination(chars);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

方法二:DFS

 import java.util.*;

public class Solution {
    private char [] seqs;
    private Integer [] book;
    //用於結果去重
    private HashSet<String> result = new HashSet<String>();
    /**
     * 輸入一個字符串,按字典序打印出該字符串中字符的所有排列。
     * 例如輸入字符串abc,則打印出由字符a,b,c
     * 所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。 結果請按字母順序輸出。
       輸入一個字符串,長度不超過9(可能有字符重複),字符只包括大小寫字母。\
     * @param str
     * @return
     */
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> arrange = new ArrayList<String>();
        if(str == null || str.isEmpty()) return arrange;
        char[] strs = str.toCharArray();
        seqs = new char[strs.length];
        book = new Integer[strs.length];
        for (int i = 0; i < book.length; i++) {
            book[i] = 0;
        }
        dfs(strs, 0);
        arrange.addAll(result);
        Collections.sort(arrange);
        return arrange;
    }

    /**
     * 深度遍歷法
     */
    private void dfs(char[] arrs, int step){
        //走完所有可能 記錄排列
        if(arrs.length == step){
            String str = "";
            for (int i = 0; i < seqs.length; i++) {
                str += seqs[i];
            }
            result.add(str);
            return; //返回上一步
        }
        //遍歷整個序列,嘗試每一種可能
        for (int i = 0; i < arrs.length; i++) {
            //是否走過
            if(book[i] == 0){
                seqs[step] = arrs[i];
                book[i] = 1;
                //下一步
                dfs(arrs, step + 1);
                //走完最後一步 後退一步
                book[i] = 0;
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

方法三:字典序算法

 import java.util.*;

//步驟如下:
//1.從這個序列中從右至左找第一個左鄰小於右鄰的字符,記錄下標爲index1 ,如果找不到,說明求解完成。
//2.從這個序列中從右至左找第一個大於str[index1]的字符,記錄下標爲index2
//3.交換index1和index2的字符,對index1+1後的所有字符進行升序排序,此時得到的即爲str按字典序的下一個排列
//4. 重複1~3的步驟,直到全部找完 

public class Solution {
    public ArrayList<String> Permutation(String str) {
           ArrayList<String> res = new ArrayList<>();

            if (str != null && str.length() > 0) {
                char[] seq = str.toCharArray();
                Arrays.sort(seq); //排列
                res.add(String.valueOf(seq)); //先輸出一個解

                int len = seq.length;
                while (true) {
                    int p = len - 1, q;
                    //從後向前找一個seq[p - 1] < seq[p]
                    while (p >= 1 && seq[p - 1] >= seq[p]) --p;
                    if (p == 0) break; //已經是“最小”的排列,退出
                    //從p向後找最後一個比seq[p]大的數
                    q = p; --p;
                    while (q < len && seq[q] > seq[p]) q++;
                    --q;
                    //交換這兩個位置上的值
                    swap(seq, q, p);
                    //將p之後的序列倒序排列
                    reverse(seq, p + 1);
                    res.add(String.valueOf(seq));
                }
            }

            return res;
        }

        public static void reverse(char[] seq, int start) {
            int len;
            if(seq == null || (len = seq.length) <= start)
                return;
            for (int i = 0; i < ((len - start) >> 1); i++) {
                int p = start + i, q = len - 1 - i;
                if (p != q)
                    swap(seq, p, q);
            }
        }

        public static void swap(char[] cs, int i, int j) {
            char temp = cs[i];
            cs[i] = cs[j];
            cs[j] = temp;
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

29、數組中出現次數超過一半的數字

題目描述 
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。

思路:O(n)的思想是,定義兩個變量result 和count,每次循環時,如果array[i]的值等於result ,則count自增,如不等並且count>0,則count自減,count==0,重新對temp賦值爲當前array[i],count賦值爲1。 
如存在大於一半的數,直接返回result 就是了,但測試數據中有不存在的情況,所以最後又來了一遍校驗,檢查當前result 值是否出現過一半以上。

public class Solution {
   public int MoreThanHalfNum_Solution(int [] array) {
       if(array==null || array.length <= 0){
           return 0;
       }

       int result = array[0];
       int count = 1;
       for (int i = 1; i < array.length; i++) {
           if (array[i] == result) {
               count++;
           } else if (count > 0 ) {
               count--;
           } else if(count == 0){
               result = array[i];
               count = 1;
           }
       }
       //驗證
       count=0;
       for(int i=0;i<array.length;i++){
           if(array[i]==result)
                count++;
       }
       return count > array.length/2 ? result : 0;
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

30、最小的K個數

題目描述 
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

思路1: 
經典常用的算法,快速排序的精髓利用快速排序劃分的思想,每一次劃分就會有一個數字位於以數組從小到達排列的的最終位置index;

位於index左邊的數字都小於index對應的值,右邊都大於index指向的值;

所以,當index > k-1時,表示k個最小數字一定在index的左邊,此時,只需要對index的左邊進行劃分即可;

當index < k - 1時,說明index及index左邊數字還沒能滿足k個數字,需要繼續對k右邊進行劃分;

//這種方式的優點是時間複雜度較小爲O(n),缺點就是需要修改輸入數組。
import java.util.ArrayList;
public class Solution {
    public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList aList = new ArrayList();
        if(input.length == 0 || k > input.length || k <= 0)
            return aList;
        int low = 0;
        int high = input.length-1;
        int index = Partition(input,k,low,high);
        while(index != k-1){
            if (index > k-1) {
                high = index-1;
                index = Partition(input,k,low,high);
            }else{
                low = index+1;
                index = Partition(input,k,low,high);
            }
        }
        for (int i = 0; i < k; i++)
            aList.add(input[i]);
        return aList;
    }

    //快速排序的分段,小於某個數的放在左邊,大於某個數的移到右邊
    public int Partition(int[] input,int k,int low,int high){
        int pivotkey = input[k-1];
        swap(input,k-1,low);
        while(low < high){
            while(low < high && input[high] >= pivotkey)
                high--;
            swap(input,low,high);
            while(low < high && input[low] <= pivotkey)
                low++;
            swap(input,low,high);
        }
        return low;
    }


    private void swap(int[] input, int low, int high) {
        int temp = input[high];
        input[high] = input[low];
        input[low] = temp;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

思路2

  • 可以先創建一個大小爲k的數據容器來存儲最小的k個數字,從輸入的n個整數中一個一個讀入放入該容器中,如果容器中的數字少於k個,按題目要求直接返回空;

  • 如果容器中已有k個數字,而數組中還有值未加入,此時就不能直接插入了,而需要替換容器中的值。按以下步驟進行插入:

    • 1、先找到容器中的最大值;
    • 2、將待查入值和最大值比較,如果待查入值大於容器中的最大值,則直接捨棄這個待查入值即可;如果待查入值小於容器中的最小值,則用這個待查入值替換掉容器中的最大值;
    • 3、重複上述步驟,容器中最後就是整個數組的最小k個數字。

-對於這個容器的實現,我們可以使用最大堆的數據結構,最大堆中,根節點的值大於它的子樹中的任意節點值。Java中的TreeSet類實現了紅黑樹的功能,它底層是通過TreeMap實現的,TreeSet中的數據會按照插入數據自動升序排列(按自然順序)。因此我們直接將數據依次放入到TreeSet中,數組就會自動排序。

public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> leastNum = new ArrayList<Integer>();
        if (input == null || input.length < 1 || k < 1 || k > input.length)
            return leastNum;
        TreeSet<Integer> kSet = new TreeSet<Integer>();
        for (int i = 0; i < input.length; i++) {
            if (kSet.size() < k) {
                kSet.add(input[i]);
            } else {
                if (input[i] < kSet.last()) {
                    kSet.remove(kSet.last());
                    kSet.add(input[i]);
                }
            }
        }
        Iterator<Integer> it = kSet.iterator();
        while (it.hasNext()) {
            leastNum.add(it.next());
        }

        return leastNum;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

31、連續子數組的最大和

題目描述: 
輸入一個整型數組,數組裏有正數和負數,數組中一個或者多個連續的數字組成一個子數組,求所有子數組的最大值,要求時間複雜度爲O(N)

思路1:

/*
算法時間複雜度O(n)
用total記錄累計值,maxSum記錄和最大
基於思想:對於一個數A,若是A的左邊累計數非負,那麼加上A能使得值不小於A,認爲累計值對
          整體和是有貢獻的。如果前幾項累計值負數,則認爲有害於總和,total記錄當前值。
此時 若和大於maxSum 則用maxSum記錄下來
*/
public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        if(array.length==0)
            return 0;
        else{
            int total=array[0],maxSum=array[0];
            for(int i=1;i<array.length;i++){
                if(total>=0)
                    total+=array[i];
                else
                    total=array[i];
                if(total>maxSum)
                    maxSum=total;
            }
            return maxSum;
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

思路2:動態規劃(相當重點)

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        //動態規劃,主要是找到狀態轉移方程
        //設f(j)是從s[0]到s[j]最大和
        //f(j) = max(s[j], f[j-1]+s[j])
        if(array.length == 0)
            return 0;
        int result = Integer_MinValue;
        int sum = 0;
        for(int i = 0; i < array.length; ++i)
        {
            sum = max(array[i], sum + array[i]);
            result = max(result, sum);
        }
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

32、整數中1出現的次數(從1到n整數中1出現的次數)

題目描述 
例如n=13的整數中1出現的次數,1~13中包含1的數字有1、10、11、12、13因此共出現6次

思路: 
一、1的數目

編程之美上給出的規律: 
1、 如果第i位(自右至左,從1開始標號)上的數字爲0,則第i位可能出現1的次數由更高位決定(若沒有高位,視高位爲0),等於更高位數字 * 當前位數的權重10^(i-1)。

2、如果第i位上的數字爲1,則第i位上可能出現1的次數不僅受更高位影響,還受低位影響(若沒有低位,視低位爲0),等於更高位數字 * 當前位數的權重10^(i-1)+(低位數字+1)。

3、如果第i位上的數字大於1,則第i位上可能出現1的次數僅由更高位決定(若沒有高位,視高位爲0),等於(更高位數字+1) * 當前位數的權重10^(i-1)。

二、X的數目 
這裏的 X∈[1,9] ,因爲 X=0 不符合下列規律,需要單獨計算。

首先要知道以下的規律: 
從 1 至 10,在它們的個位數中,任意的 X 都出現了 1 次。 
從 1 至 100,在它們的十位數中,任意的 X 都出現了 10 次。 
從 1 至 1000,在它們的百位數中,任意的 X 都出現了 100 次。

依此類推,從 1 至 10^ i ,在它們的左數第二位(右數第 i 位)中,任意的 X 都出現了 10^(i-1) 次。

這個規律很容易驗證,這裏不再多做說明。 
接下來以 n=2593,X=5 爲例來解釋如何得到數學公式。從 1 至 2593 中,數字 5 總計出現了 813 次,其中有 259 次出現在個位,260 次出現在十位,294 次出現在百位,0 次出現在千位。

現在依次分析這些數據, 
首先是個位。從 1 至 2590 中,包含了 259 個 10,因此任意的 X 都出現了 259 次。最後剩餘的三個數 2591, 2592 和 2593,因爲它們最大的個位數字 3 < X,因此不會包含任何 5。(也可以這麼看,3 < X,則個位上可能出現的X的次數僅由更高位決定,等於更高位數字(259)*10^(1-1)=259)。

然後是十位。從 1 至 2500 中,包含了 25 個 100,因此任意的 X 都出現了 25×10=250 次。剩下的數字是從 2501 至 2593,它們最大的十位數字 9 > X,因此會包含全部 10 個 5。最後總計 250 + 10 = 260。(也可以這麼看,9>X,則十位上可能出現的X的次數僅由更高位決定,等於更高位數字(25+1)*10^(2-1)=260)。

接下來是百位。從 1 至 2000 中,包含了 2 個 1000,因此任意的 X 都出現了 2×100=200 次。剩下的數字是從 2001 至 2593,它們最大的百位數字 5 == X,這時情況就略微複雜,它們的百位肯定是包含 5 的,但不會包含全部 100 個。如果把百位是 5 的數字列出來,是從 2500 至 2593,數字的個數與百位和十位數字相關,是 93+1 = 94。最後總計 200 + 94 = 294。(也可以這麼看,5==X,則百位上可能出現X的次數不僅受更高位影響,還受低位影響,等於更高位數字(2)*10^(3-1)+(93+1)=294)。

最後是千位。現在已經沒有更高位,因此直接看最大的千位數字2< X,所以不會包含任何 5。(也可以這麼看,2< X,則千位上可能出現的X的次數僅由更高位決定,等於更高位數字(0)*10^(4-1)=0)。 
到此爲止,已經計算出全部數字 5 的出現次數。

總結一下以上的算法,可以看到,當計算右數第 i 位包含的 X 的個數時:

  • 1、取第 i 位左邊的數字(高位),乘以 10 ^(i−1) ,得到基礎值 a 。
  • 2、取第 i 位數字,計算修正值: 
    • 1、如果大於 X,則結果爲 a+ 10 ^(i−1) 。
    • 2、如果小於 X,則結果爲 a 。
    • 3、如果等 X,則取第 i 位右邊(低位)數字,設爲 b ,最後結果爲 a+b+1 。

相應的代碼非常簡單,效率也非常高,時間複雜度只有 O( log 10 n) 。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        //當x=1時
        return NumberOfXBetween1AndN_Solution1(n,1);       
    }

 /**
 * @param n
 * @param x [1,9]
 * @return (從1到n整數中x出現的次數)
 */
    public int NumberOfXBetween1AndN_Solution1(int n,int x) {
        if(n<0||x<1||x>9)
            return 0;
        int high,low,curr,tmp,i = 1;
        high = n;
        int total = 0;
        while(high!=0){
            high = n/(int)Math.pow(10, i);// 獲取第i位的高位
            tmp = n%(int)Math.pow(10, i);
            curr = tmp/(int)Math.pow(10, i-1);// 獲取第i位
            low = tmp%(int)Math.pow(10, i-1);// 獲取第i位的低位
            if(curr==x){
                total+= high*(int)Math.pow(10, i-1)+low+1;
            }else if(curr<x){
                total+=high*(int)Math.pow(10, i-1);
            }else{
                total+=(high+1)*(int)Math.pow(10, i-1);
            }
            i++;
        }
        return total;       
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

33、把數組排成最小的數

題目描述: 
輸入一個正整數數組,把數組裏所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字爲321323。

package cn.zhuang.offer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class Main {

/* 解題思路:
 * 考慮到大數問題,先將整型數組轉換成String數組,然後將String數組排序,最後將排好序的字符串數組拼接出來。關鍵就是制定排序規則。
 * 排序規則如下:
 * 若ab > ba 則 a > b,
 * 若ab < ba 則 a < b,
 * 若ab = ba 則 a = b;
 * 解釋說明:
 * 比如 "3" < "31"但是 "331" > "313",所以要將二者拼接起來進行比較
 */
    public static String PrintMinNumber(int [] numbers) {
        if(numbers == null || numbers.length == 0) return "";
        int len = numbers.length;
        String[] str = new String[len];
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < len; i++){
            str[i] = String.valueOf(numbers[i]);
        }

        //此處較爲重要,新的排序規則,如若取最大值,~c1.compareTo(c2)
        Arrays.sort(str,new Comparator<String>(){
            @Override
            public int compare(String s1, String s2) {
                String c1 = s1 + s2;
                String c2 = s2 + s1;
                return c1.compareTo(c2);
            }
        });
        for(int i = 0; i < len; i++){
            sb.append(str[i]);
        }
        return sb.toString();
    }

    public static void main(String[] args){
        int[] a = {3,32,421};
        System.out.println(PrintMinNumber(a));
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

java中的compareto方法,返回參與比較的前後兩個字符串的asc碼的差值,看下面一組代碼 
String a=”a”,b=”b”; 
System.out.println(a.compareto.b); 
則輸出-1; 
若a=”a”,b=”a”則輸出0; 
若a=”b”,b=”a”則輸出1;

單個字符這樣比較,若字符串比較長呢?? 
若a=”ab”,b=”b”,則輸出-1; 
若a=”abcdef”,b=”b”則輸出-1; 
也就是說,如果兩個字符串首字母不同,則該方法返回首字母的asc碼的差值

如果首字母相同呢?? 
若a=”ab”,b=”a”,輸出1; 
若a=”abcdef”,b=”a”輸出5; 
若a=”abcdef”,b=”abc”輸出3; 
若a=”abcdef”,b=”ace”輸出-1; 
即參與比較的兩個字符串如果首字符相同,則比較下一個字符,直到有不同的爲止,返回該不同的字符的asc碼差值,如果兩個字符串不一樣長,可以參與比較的字符又完全一樣,則返回兩個字符串的長度差值

34、醜數

題目描述: 
把只包含因子2、3和5的數稱作醜數(Ugly Number)。例如6、8都是醜數,但14不是,因爲它包含因子7。習慣上我們把1當做是第一個醜數。求按從小到大的順序的第N個醜數。

思路1:最簡單的方法就是先通過將一個數不斷除以2,3,5來判定該數是不是醜數,而後在從1開始,依次往後判斷每個數是不是醜數,並記下醜數的個數,這樣當計算的個數爲給定值時,便是需要求的第n個醜數,這種方法的時間複雜度爲O(k),這裏的k爲第n個醜數的大小,比如第1500個醜數的大小爲859963392,那麼就需要判斷859963392次,時間效率非常低。

public boolean IsUgly(int number)
{
    while(number % 2 == 0)
        number /= 2;
    while(number % 3 == 0)
        number /= 3;
    while(number % 5 == 0)
        number /= 5;

    return (number == 1) ? true : false;
}

public int GetUglyNumber(int index)
{
    if(index <= 0)
        return 0;

    int number = 0;
    int uglyFound = 0;
    while(uglyFound < index)
    {
        ++number;

        if(IsUgly(number))
        {
            ++uglyFound;
        }
    }

    return number;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

思路Better2:直觀的優化措施就是看能不能將時間複雜度降低到O(n),即只在醜數上花時間,而不在非醜數上浪費時間。劍指offer上給的思路很好,用O(n)的輔助空間來得到O(n)的時間複雜度。其核心思想是:每一個醜數必然是由之前的某個醜數與2,3或5的乘積得到的,這樣下一個醜數就用之前的醜數分別乘以2,3,5,找出這三這種最小的並且大於當前最大丑數的值,即爲下一個要求的醜數。

import java.util.*;
public class Solution
{
    public int GetUglyNumber_Solution(int n)
    {
        if(n==0)return 0;
        ArrayList<Integer> res=new ArrayList<Integer>();
        res.add(1);
        int i2=0,i3=0,i5=0;
        while(res.size()<n)
        {
            int m2=res.get(i2)*2;
            int m3=res.get(i3)*3;
            int m5=res.get(i5)*5;
            int min=Math.min(m2,Math.min(m3,m5));
            res.add(min);
            if(min==m2)i2++;
            if(min==m3)i3++;
            if(min==m5)i5++;
        }
        return res.get(res.size()-1);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

35、第一個只出現一次的字符

描述:在一個字符串(1<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符的位置。若爲空串,返回-1。位置索引從0開始,如“abaccdrff”,則輸出“b”

第一種,數組方法:

public class Solution {
   public int FirstNotRepeatingChar(String str) {
        if (str.length() == 0) {
            return  -1;
        }
        char c = 'A';
        if(str.charAt(0) >= 'a'){
            c = 'a';
        }
        int[] counts = new int[26];
        for (int i = 0; i < str.length(); i++) {
            counts[str.charAt(i) - c]++;
        }
        for (int i = 0; i < str.length(); i++) {
            if (counts[str.charAt(i) - c] == 1){
                return i;
            }
        }
        return -1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第二種,HashMap方法:

import java.util.LinkedHashMap;
// use linkedhashmap to keep the order
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        LinkedHashMap <Character, Integer> map = new LinkedHashMap<Character, Integer>();
        for(int i=0;i<str.length();i++){
            if(map.containsKey(str.charAt(i))){
                int time = map.get(str.charAt(i));
                map.put(str.charAt(i), ++time);
            }
            else {
                map.put(str.charAt(i), 1);
            }
        }
        int pos = -1;  
        int i=0;
        for(;i<str.length();i++){
            char c = str.charAt(i);
            if (map.get(c) == 1) {
                return i;
            }
        }
        return pos;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

36、數組中的逆序對

題目描述: 
在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。 
{7,5,6,4},則76,75,74,64,65,一共5個

思路: 
這裏寫圖片描述

public class Solution {
     private  int reversePair = 0;
    public int InversePairs(int [] array) {
        if(array==null)
            return 0;
        int len = array.length;
        if(len==0)
            return 0;
        sort(array,0,len-1);
        return reversePair;
    }

    private void sort(int[]arr,int start,int end){
        if(start<end){
            int mid = start+(end-start)/2;
            sort(arr,start,mid);
            sort(arr,mid+1,end);
            merger(arr,start,mid,mid+1,end);
        }
    }

    private void merger(int[] arr, int start1, int end1, int start2, int end2) {
        int len= end2-start1+1;
        int[] anx = new int[len];
        int k = end2-start1+1;

        int i = end1;
        int j= end2;
        while(i>=start1&j>=start2){
            if(arr[i]>arr[j]){
                anx[--k]=arr[i];
                i--;
                reversePair = reversePair+(j-start2+1);
            }
            else{
                anx[--k]=arr[j];
                j--;
            }
        }
        for(;i>=start1;i--)
            anx[--k]=arr[i];
        for(;j>=start2;j--)
            anx[--k]=arr[j];

        for(int m=0;m<len;m++)
            arr[start1++]=anx[m];

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

37、兩個鏈表的第一個公共交點

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
       if (pHead1 == null||pHead2 == null) {
            return null;
        }
        int count1 = 0;
        ListNode p1 = pHead1;
        while (p1!=null){
            p1 = p1.next;
            count1++;
        }
        int count2 = 0;
        ListNode p2 = pHead2;
        while (p2!=null){
            p2 = p2.next;
            count2++;
        }
        int flag = count1 - count2;
        if (flag > 0){
            while (flag>0){
                pHead1 = pHead1.next;
                flag --;
            }
        while (pHead1!=pHead2){
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return pHead1;
    }
        if (flag <= 0){
            while (flag<0){
                pHead2 = pHead2.next;
                flag ++;
            }
            while (pHead1 != pHead2){
                pHead2 = pHead2.next;
                pHead1 = pHead1.next;
            }
            return pHead1;
        }
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

38、數字在排序數組中出現的次數

思路:重點是有序數組這個條件,採用二分查找法,分別找到第一個和最後一個,這樣無論最好最壞複雜度都是O(lgN)

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
      int num = 0;
        if (array != null && array.length > 0) {
            int firstKIndex = getFirstK(array, k, 0, array.length - 1);
            int lastKIndex = getLastK(array, k, 0, array.length - 1);
            if (firstKIndex > -1 && lastKIndex > -1)
                num = lastKIndex - firstKIndex + 1;
        }
        return num;
    }

    /*
    * 找到第一個出現的數字的下標
    */
    public  int getFirstK(int[] array, int k, int start, int end) {
        if (start > end)
            return -1;
        int middleIndex = start + (end - start) / 2;
        int middleData = array[middleIndex];
        if (middleData == k) {
        //判斷是不是第一個K,前一個不等於K,就是第一個K
            if (middleIndex > 0 && array[middleIndex - 1] != k || middleIndex == 0) {
                return middleIndex;
            } else
                end = middleIndex - 1;
        } else if (middleData > k) {
            end = middleIndex - 1;
        } else
            start = middleIndex + 1;
        return getFirstK(array, k, start, end);
    }

     /*
    * 找到最後一個出現的數字的下標
    */
    public  int getLastK(int array[], int k, int start, int end) {
        if (start > end) {
            return -1;
        }
        int middleIndex = (start + end) / 2;
        int middleData = array[middleIndex];
        if (middleData == k) {
         //判斷是不是最後一個K,後一個不等於K,就是最後一個K
            if (middleIndex < array.length - 1 && array[middleIndex + 1] != k || middleIndex == array.length - 1)
                return middleIndex;
            else
                start = middleIndex + 1;
        } else if (middleData < k) {
            start = middleIndex + 1;
        } else
            end = middleIndex - 1;
        return getLastK(array, k, start, end);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

39、二叉樹的深度

題目描述: 
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。

思路:遞歸,下一層根節點到上一層根節點,深度加1,判斷左子樹和右子樹的最大值,然後+1

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
};*/
public class Solution {
     public int getHeight(TreeNode root) {
        if (root == null)
            return 0;
        return max(getHeight(root.left), getHeight(root.right)) + 1;
    }

    private int max(int a, int b) {
        return (a > b) ? a : b;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

擴展題:判斷二叉樹平衡

描述:如果某二叉樹中任意節點的左右子樹的深度相差不超過1,那麼它就是一棵平衡二叉樹。

第一種思路:簡潔,但是效率不高,因爲會重複遍歷子節點

public class Solution {
   public boolean IsBalanced(TreeNode root) {
        if (root == null)
            return true;

        if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1)
            return false;


        return IsBalanced(root.left) && IsBalanced(root.right);

    }

    public int getHeight(TreeNode root) {
        if (root == null)
            return 0;
        return max(getHeight(root.left), getHeight(root.right)) + 1;
    }

    private int max(int a, int b) {
        return (a > b) ? a : b;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

第二種Better思路:從底向上判斷,這樣可以記錄下一層的深度

public class Solution {
  public boolean IsBalanced(TreeNode root) {
        int depth = 0;
        return IsBalanced(root, depth);
    }

    public boolean IsBalanced(TreeNode root, int depth) {
        if (root == null) {
            depth = 0;
            return true;
        }

        int left = 0, right = 0;
        if (IsBalanced(root.left, left) && IsBalanced(root.right, right)) {
            int diff = left - right;
            if (diff <= 1 && diff >= -1) {
                depth = 1 + (left > right ? left : right);
                return true;
            }
        }

        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

40-數組只指出現一次的數字:

異或去重:

適應情景:數組中只有一個數字出現了奇數次,其他的都出現了偶數次。或者只有一個數字出現了偶數次,其他的都出現了奇數次 
性質:對於任意的a:a^a=0,a^0=a,a^(-1)=~a。

下面來看三道題目:

1、1-1000放在含有1001個元素的數組中,只有唯一的一個元素值重複,其它均只出現一次。每個數組元素只能訪問一次,設計一個算法,將它找出來;不用輔助存儲空間,能否設計一個算法實現?

當然,這道題,可以用最直觀的方法來做,將所有的數加起來,減去1+2+3+…+1000的和,得到的即是重複的那個數,該方法很容易理解,而且效率很高,也不需要輔助空間,唯一的不足時,如果範圍不是1000,而是更大的數字,可能會發生溢出。

我們考慮用異或操作來解決該問題。現在問題是要求重複的那個數字,我們姑且假設該數字式n吧,如果我們能想辦法把1-1000中除n以外的數字全部異或兩次,而數字n只異或一次,就可以把1-1000中出n以外的所有數字消去,這樣就只剩下n了。我們首先把所有的數字異或,記爲T,可以得到如下:

T = 1^2^3^4…^n…^n…^1000 = 1^2^3…^1000(結果中不含n)

而後我們再讓T與1-1000之間的所有數字(僅包含一個n)異或,便可得到該重複數字n。如下所示:

T^(a^2^3^4…^n…^1000) = T^(T^n) = 0^n = n

2、一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。

樣例輸入:2 4 3 6 3 2 5 5 
樣例輸出:4 6

思路:異或去重是知道如果只有一個只出現一次的數字的求法,但這裏是有兩個只出現一次的數字,我們便要想辦法把他分爲兩個子數組,每個子數組中包含一個只出現一次的數字,其他的數字都出現了兩次。

首先依然從頭到尾異或所有的數字,如ABCCDDEEFF,這樣得到的結果就是AB異或的結果,那麼在異或後的結果中找出其二進制中最右邊爲1的位,該位既然爲1,說明AB對應的該位肯定不同,必定一個爲1,一個爲0,因此我們可以考慮根據此位是否爲1來劃分這兩個子數組,這樣兩個只出現一次的數字就分開了

但我們還要保證出現兩次的數字都分到同一個子數組中,很明顯,相同的數字相同的位上的值是相同的,要麼都爲1,要麼都爲0,因此我們同樣可以通過判斷該位是否爲1來將這些出現兩次的數字劃分到同一個子數組中,該位如果爲1,就分到一個子數組中,如果爲0,就分到另一個子數組中。

這樣就能保證每個子數組中只有一個出現一次的數字,其他的數字都出現兩次,分別全部異或即可得到這兩個只出現一次的數字。時間複雜度爲O(n)。

另外,所有元素異或後,在找出最右邊爲1的時,****X&(-X)之後得到的數字,是把X中最右邊的1保留下來

注意,這裏的-X是X的相反數,-X是對X所有位取反+1

package cn.zhuang.offer;

public class Main {

    public static void FindNumsAppearOnce(int[] arr)  
    {  
        int len = arr.length;
        if(len<2)  
            return;  

        int i;  
        int AllXOR = 0;  
        //全部異或  
        for(i=0;i<len;i++)  
            AllXOR ^= arr[i];  

      //找出第幾位爲1,如010
        int res = FindFirstBit1(AllXOR);  

        int num1=0, num2 = 0;  
        for(i=0;i<len;i++)  
        {  //分成了兩組
            if(IsBit1(arr[i],res))  
                num1 ^= arr[i];  
            else  
                num2 ^= arr[i];  
        }  
        System.out.println(num1+"and"+num2);
    }

    /* 
    返回num的最低位的1,其他各位都爲0 
    */  
    public static int FindFirstBit1(int num)  
    {  
        //二者與後得到的數,將num最右邊的1保留下來,其他位的全部置爲了0  
        return num & (-num);  
    }  

    /* 
    判斷data中特定的位是否爲1, 
    這裏的要判斷的特定的位由res確定, 
    res中只有一位爲1,其他位均爲0,由FindFirstBit1函數返回, 
    而data中要判斷的位便是res中這唯一的1所在的位 
    */  
    public static boolean IsBit1(int data,int res)  
    {  
        return ((data & res)==0) ? false:true;  
    }  

    public static void main(String[] args) {
        int[] a = { 1,1,2,2,3,44,55,55,66,66,34,34,5,5,7,7 };
        FindNumsAppearOnce(a);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

3、題目:一個int數組中有三個數字a、b、c只出現一次,其他數字都出現了兩次。請找出三個只出現一次的數字。

通用性的方法,對於2個,3個出現一次的數字這類的問題,都可以按照該思路去求解,只是時間複雜度可能要稍微大些,爲O(8* sizeof(int)* N),8位* 4字節* N自然就是數組的長度了。

該方法的思路如下:

首先由於有3個數字出現一次,其他的都出現兩次,所以N肯定爲奇數該方法通過掃描整數的每一位來逐個判斷。

再看這3個只出現一次的數字,他們的bit位肯定不可能全部相同,至少有一個位,要麼110,要麼001,我們可以通過掃面int的所有bit位,掃描每個bit位的時候,遍歷數組,首先找到一個,另外的兩個就可以按照上題的解法。

如何找到第一個數?

我們遍歷數組,分別統計該數組元素中該bit位爲1和0的元素個數,分別設爲count1和count0,並同時將所有該bit位爲1的元素異或結果爲temp1,所有該bit位爲0的元素異或,得到的結果爲temp0。

如果111或000這兩種狀態,肯定三個都在count爲奇數的裏邊,不好區分,主要找110或001的區分

先看第一種情況,001,如果count1爲奇數,則爲111或001,則在count1的數組裏,如果temp0==0,則爲111,此位不是判斷的位,下一位,如果temp0==1,則爲001,temp1則爲所求。

先看第二種情況,110,如果count1爲偶數,則爲110或000,則在count1的數組裏,如果temp1==0,則爲000,此位不是判斷的位,下一位,如果temp1==1,則爲110,temp0則爲所求。

說白了,就是看count1爲奇數時,temp0是否爲1,count1爲偶數時,temp1是否爲1

package cn.zhuang.offer;

public class Main {

    /*
     * 找出這三個只出現一次的數字
     */
    public static void FindThreeNumsAppearOnce(int[] arr, int len) {
        if (len < 3)
            return;

        int num1 = FindOneNumAppearOnce(arr, len);
        System.out.println(num1);

        // 找到第一個找出的數字,並與最後一個元素交換,便於接下來剩下的兩個數字
        int i;
        for (i = 0; i < len; i++){
            if (num1 == arr[i]){
                int temp ;
                temp = arr[i];
                arr[i] = arr[len -1];
                arr[len - 1]=temp;
                break;
            }
        }

        FindTwoNumsAppearOnce(arr, len - 1);
    }

    /*
     * 通過掃面每一位,先找出一個只出現一次的數
     */
    public static int FindOneNumAppearOnce(int[] arr, int len) {
        int count1 = 0; // 某一位上1的個數
        int count0 = 0; // 某一位上0的個數
        int temp1 = 0; // 某一位爲1的所有數相異或的結果
        int temp0 = 0; // 某一位爲0的所有數相異或的結果

        int i, j;
        // 循環計算每一位的以上四個數據
        for (i = 0; i < 32; i++) {
            count1 = count0 = temp1 = temp0 = 0;// 每次計算下一位時清零
            for (j = 0; j < len; j++) {
                // 每次向左移一位進行計算
                if ((arr[j] & (1 << i)) != 0) {
                    temp1 ^= arr[j];
                    count1++;
                } else {
                    temp0 ^= arr[j];
                    count0++;
                }
            }

            if ((temp1 & 1) != 0) {
                // 某位上有奇數個1
                if (temp0 == 0) // 此時3個不同數的該位都爲1
                    continue;
                else
                    // 此時3個不同數的該位有1個1,2個0
                    return temp1;
            } else {
                // 某位上有偶數個1
                if (temp1 == 0) // 此時3個不同數的該位都爲0
                    continue;
                else
                    // 此時3個不同數的該位有1個0,2個1
                    return temp0;
            }
        }

        return Integer.MAX_VALUE;
    }

    public static void FindTwoNumsAppearOnce(int[] arr, int len) {

        int i;
        int AllXOR = 0;
        // 全部異或
        for (i = 0; i < len; i++)
            AllXOR ^= arr[i];

        int res = FindFirstBit1(AllXOR);

        int num1 = 0;
        int num2 = 0;

        for (i = 0; i < len; i++) {
            if (IsBit1(arr[i], res))
                num1 ^= arr[i];
            else
                num2 ^= arr[i];
        }

        System.out.println(num1);
        System.out.println(num2);
    }

    /*
     * 返回num的最低位的1,其他各位都爲0
     */
    public static int FindFirstBit1(int num) {
        // 二者與後得到的數,將num最右邊的1保留下來,其他位的全部置爲了0
        return num & (-num);
    }

    /*
     * 判斷data中特定的位是否爲1, 這裏的要判斷的特定的位由res確定, res中只有一位爲1,其他位均爲0,由FindFirstBit1函數返回,
     * 而data中要判斷的位便是res中這唯一的1所在的位
     */
    public static boolean IsBit1(int data, int res) {
        return ((data & res) == 0) ? false : true;
    }

    public static void main(String[] args) {
        int[] a = { 1,2,2,3,44,55,55,66,66,34,34,5,5,7,7 };
        FindThreeNumsAppearOnce(a, a.length);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

41、和爲S的兩個數字

題目描述 
輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,任意輸出一對。 
輸入數組{1,2,4,7,11,15}和15,輸出4,11

import java.util.ArrayList;
public class Solution {
    /*
    * i,j分別表示數組兩端下表
    * 當array[i]+array[j]>S時,j-- 尾端向前移動,兩數據和增大
    * 當array[i]+array[j]=S時,將array[i],array[j]依次添加到ArrayList中
    * 當array[i]+array[j]<S時,i++前段向後移動,兩數據和減小
    */
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        if (array == null || array.length < 2) {
            return list;
        }
        int i=0,j=array.length-1;
        while(i<j){
            if(array[i]+array[j]==sum){
            list.add(array[i]);
            list.add(array[j]);
                return list;
           }else if(array[i]+array[j]>sum){
                j--;
            }else{
                i++;
            }

        }
        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

擴展題:和爲S的連續正數序列

描述:輸出所有和爲S的連續正數序列。序列內按照從小至大的順序,序列間按照開始數字從小到大的順序

import java.util.ArrayList;

public class Solution {
    public static ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
        if (sum < 3)
            return list;
        ArrayList<Integer> l = new ArrayList<Integer>();
        int small = 2;
        int middle = (1 + sum) / 2;//因爲至少連續2個且順序增加,所以取中間值
        l.add(1);
        l.add(2);
        int s = 3;
        if (s == sum) {
            list.add(new ArrayList<Integer>(l));
        }

        while (small <= middle) {
            small++;
            s += small;
            l.add(small);
            if (s == sum) {
                list.add(new ArrayList<Integer>(l));
            }
      //兩個指針,若大,減去左邊數字,若小,加右邊數字
            while (s > sum && small <= middle) {
                s -= l.remove(0);
                if (s == sum) {
                    list.add(new ArrayList<Integer>(l));
                }
            }

        }
        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

42、反轉單詞

描述:反轉英文單詞,例如,“student. a am I”反轉成“I am a student.”

思想:就是先翻轉所有字符,再逐個單詞翻轉

public class Solution {
     public String ReverseSentence(String str) {
         if(str==null||str.trim().equals(""))// trim掉多餘空格
             return str;
         String[] words = str.split(" ");// 以空格切分出各個單詞
         StringBuffer buffer = new StringBuffer();
         for(int i=0;i<words.length;i++){

             buffer.append(reverse1(words[i].toCharArray(), 0, words[i].length()-1)).append(" ");
         }
         if(buffer.length()>0)
             buffer.deleteCharAt(buffer.length()-1);// 刪除最後一個空格
         return reverse1(buffer.toString().toCharArray(), 0, buffer.length()-1);

 }
    private String reverse1(char[] str, int l, int r) {
         // TODO Auto-generated method stub
         if(l>r)
             return "";
         char tmp;
         while(l<r){
             tmp = str[l];
             str[l] = str[r];
             str[r] = tmp;
             l++;
             r--;
         }
         return String.valueOf(str);
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

擴展題:字符串左移n位,abcde,左移2位,cdeab

思路:前n位反轉,後幾位反轉,最後總的反轉

public class Solution {
    public String LeftRotateString(String str,int n) {
        char[] chars = str.toCharArray();        
        if(chars.length < n) return "";
        reverse(chars, 0, n-1);
        reverse(chars, n, chars.length-1);
        reverse(chars, 0, chars.length-1);
        StringBuilder sb = new StringBuilder(chars.length);
        for(char c:chars){
            sb.append(c);
        }
        return sb.toString();
    }

    public void reverse(char[] chars,int low,int high){
        char temp;
        while(low<high){
            temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

44、撲克牌的順序

從撲克牌中抽取5張牌,判斷是否連續,大小王是任意數字

思路:選取5張牌,首先去0,然後進行排序,最大值減最小值是否小於等於4,大於4,爲false, 
然後相鄰相減應該大於0小於5,否的爲false

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length == 0 || numbers.length > 5){
            return false;
        }
        ArrayList<Integer> al = new ArrayList<>();
        int len = numbers.length;
        int count = 0;
        for(int i = 0; i < len; i++){
        //必須去0,因爲0可以是任意數字,如0,2,3,5,6,也是連續的
            if(0 == numbers[i]){
                count++;
            }else{
                al.add(numbers[i]);
            }      
        }
         //非0的排序
        Collections.sort(al);
        int len1 = al.size();
        //大於4,肯定false
        if(Math.abs(al.get(0) - al.get(len1 - 1)) > 4){
            return false;
        }
        for(int i = 0; i < len1 - 1; i++){
         //相鄰的只差,大於0不能重複,大於4肯定false
            int temp = al.get(i + 1) - al.get(i);
            if(0 < temp && temp < 5){
                continue;
            }else{
                return false;
            }
        }
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

45、約瑟夫環問題:圓圈中最後一個數字

題目描述:一個環,每次刪除第m個數字,求最後一個數字,如0,1,2,3,4這5個數字,從0開始每次刪除第3個數字,則依次刪除2,0,4,1,最後一個數字是3

第一種解法:數組O(m*N)

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n<1||m<1) return -1;
        int[] array = new int[n];
        int i = -1,step = 0, count = n;
        while(count>0){   //跳出循環時將最後一個元素也設置爲了-1
            i++;          //指向上一個被刪除對象的下一個元素。
            if(i>=n) i=0;  //模擬環。
            if(array[i] == -1) continue; //跳過被刪除的對象。
            step++;                     //記錄已走過的。
            if(step==m) {               //找到待刪除的對象。
                array[i]=-1;
                step = 0;
                count--;
            }        
        }
        return i;//返回跳出循環時的i,即最後一個被設置爲-1的元素
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

第二種:鏈表,O(N)

import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if (m == 0 || n == 0) {
            return -1;
        }
        ArrayList<Integer> data = new ArrayList<Integer>();
        for (int i = 0; i < n; i++) {
            data.add(i);
        }
        int index = -1;
        while (data.size() > 1) {
        //重點是此步
            index = (index + m) % data.size();
            data.remove(index);
            index--;
        }
        return data.get(0);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

第三種better解法:約瑟夫經典解法,O(N),空間複雜度O(1)

public class Solution {
    public int LastRemaining_Solution(int n,int m) {
        if(n==0) return -1;

       int s=0;
       for(int i=2;i<=n;i++){
           s=(s+m)%i;
       }
       return s;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

46、求1+2+3+…+n

題目描述:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

public class Solution {

    public int Sum_Solution(int n) {
        int result = 0;
        int a = 1;
        boolean value = ((n!=0) && a == (result = Sum_Solution(n-1)));
        result += n;    
        return result;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

47、不用加減乘除做加法

題目描述 
寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。

思路:首先看十進制是如何做的: 5+7=12,三步走

第一步:相加各位的值,不算進位,得到2。

第二步:計算進位值,得到10. 如果這一步的進位值爲0,那麼第一步得到的值就是最終結果。

第三步:重複上述兩步,只是相加的值變成上述兩步的得到的結果2和10,得到12。

同樣我們可以用三步走的方式計算二進制值相加: 5-101,7-111 第一步:相加各位的值,不算進位,得到010,二進制每位相加就相當於各位做異或操作,101^111。

第二步:計算進位值,得到1010,相當於各位做與操作得到101,再向左移一位得到1010,(101&111)<<1。

第三步重複上述兩步, 各位相加 010^1010=1000,進位值爲100=(010&1010)<<1。 
繼續重複上述兩步:1000^100 = 1100,進位值爲0,跳出循環,1100爲最終結果。

public class Solution {
    public int Add(int num1,int num2) {
        while (num2!=0) {
            int temp = num1^num2;//不算進位,各位相加
            num2 = (num1&num2)<<1;//得到進位數
            num1 = temp;//與進位數相加,即循環以上操作
        }
        return num1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

49、把字符串轉換成整數

此題主要是注意細節: 
1、功能測試:輸入有+-號情況,區分正負數和0 
2、特殊輸入:空字符串情況,輸入非數字字符串情況,如a12 
3、邊界值:最大正整數和最小負整數溢出情況

public class Solution {
    public int StrToInt(String str)
    {
        if (str.equals("") || str.length() == 0)//空字符串情況
            return 0;
        char[] a = str.toCharArray();
        int i = 0;
        boolean fuhao = true;//+-符號位
        if (a[0] == '-'){
            fuhao = false;
            i = 1;//第一位如果是-號,則從第二位開始循環
        }           
        int sum = 0;
        for (; i < a.length; i++)
        {
            if (a[i] == '+')
                continue;
            if (a[i] < 48 || a[i] > 57)
                return 0;//有非數字字符
            sum = sum * 10 + a[i] - 48;

            //判斷是否溢出,正整數最大0X7FFF FFFF,最小負整數0X8000 0000
            if((fuhao && sum > 0X7fffffff) || (!fuhao && sum < 0X80000000)){
                sum = 0;
                break;
            }

        }
        return fuhao ? sum : sum * -1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

51、數組中重複的數

題目描述 
在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是重複的數字2或者3。

思路1:排序,時間複雜度O(NlogN)

思路2:Hash表,時間和空間複雜度都是O(N)

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public class Solution {

    boolean duplicate(int numbers[],int length,int [] duplication) {
        HashMap<Integer, Integer> countMap = new HashMap<Integer, Integer>();
        if(length < 2||numbers==null){
            return false;
        }
        int j = 1;
        for(int i = 0;i < length;i++){
            if(countMap.get(numbers[i]) == null){
                j = 1;
                countMap.put(numbers[i], j);
            }else{
                j = countMap.get(numbers[i]);
                j++;
                countMap.put(numbers[i], j);
            }
        }
        Iterator iter = countMap.entrySet().iterator();
        while(iter.hasNext()){
            Entry<Integer, Integer> entry = (Entry<Integer, Integer>) iter.next();
            Integer key = entry.getKey();
            Integer val = countMap.get(key);
            if(val > 1){
                duplication[0] = key;
                return true;
            }
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

思路3:用Set集合,因爲Set集合不允許有重複的,時間和空間複雜度都是O(N)

import java.util.Set;
import java.util.HashSet;
public class Solution {
    boolean duplicate(int numbers[], int length, int[] duplication) {
        if(length < 2||numbers==null){
            return false;
        }

        Set<Integer> ss = new HashSet<Integer>();
        for (int i = 0; i < numbers.length; i++) {
            if (ss.contains(numbers[i])) {
                duplication[0] = numbers[i];
                return true;
            } else {
                ss.add(numbers[i]);
            }
        }
        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

思路4better:時間複雜度O(N),所有操作都是在輸入數組上進行,所以不需要分配額外空間,空間複雜度爲O(1)

這裏寫圖片描述

public class Solution {

    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null||length<0)return false;

        for(int i = 0;i < length; i++){
            if(numbers[i]<0||numbers[i]>length-1)
                return false;
        }

        for(int i = 0;i< length;i++){
            while(numbers[i]!=i){
                if(numbers[i]==numbers[numbers[i]]){
                    duplication[0] = numbers[i];
                    return true;
                }
                else{
                    //沒有找到,然後則交換,使該數到正確的位置去
                    swap(numbers,i,numbers[i]);
                }
            }

        }
         return false;
    }

    //交換二數
    private void swap(int[]a, int i, int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

52、構建乘積數組

題目描述 
給定一個數組A[0,1,…,n-1],請構建一個數組B[0,1,…,n-1],其中B中的元素B[i]=A[0] * A[1] * … * A[i-1]* A[i+1]* …*A[n-1]。不能使用除法。

思路:1.計算前i - 1個元素的乘積,及後N - i個元素的乘積分別保存在兩個數組中

這裏寫圖片描述

import java.util.ArrayList;
public class Solution {
   public int[] multiply(int[] A) {
        int len = A.length;
        int forword[] = new int[len];
        int backword[] = new int[len];
        int B[] = new int[len];
        forword[0] = 1;
        backword[0] = 1;
        for(int i = 1;i < len; i++){
            forword[i] = A[i - 1]*forword[i-1];
            backword[i] = A[len - i]*backword[i - 1];
        }
        for(int i = 0; i < len; i++){
            B[i] = forword[i] * backword[len - i -1];
        }
        return B;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

53、正則表達式匹配

題目描述 
請實現一個函數用來匹配包括’.’和’* ‘的正則表達式。模式中的字符’.’表示任意一個字符,而’* ’ 表示它前面的字符可以出現任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串”aaa”與模式”a.a”和”ab* ac* a”匹配,但是與”aa.a”和”ab*a”均不匹配

public class Solution {
    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;
        }
        //str未到尾,pattern到尾,匹配失敗
        if (strIndex != str.length && patternIndex == pattern.length) {
            return false;
        }
        //str到尾,pattern未到尾(不一定匹配失敗,因爲a*可以匹配0個字符)
        if (strIndex == str.length && patternIndex != pattern.length) {
            //只有pattern剩下的部分類似a*b*c*的形式,才匹配成功
            if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
                return matchCore(str, strIndex, pattern, patternIndex + 2);
            }
            return false;
        }

        //str未到尾,pattern未到尾
        if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
            if (pattern[patternIndex] == str[strIndex] || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            //三種可能:
                //1、模式串當前字符出現0次,即*表示當前字符出現0次,則str=str,pattern=pattern+2;
                //2、模式串當前字符出現1次,即*表示當前字符出現1次,則str=str+1,pattern=pattern+2;
                //3、模式串當前字符出現2次或2次以上,即*表示當前字符出現2次或以上,則str=str+1,pattern=pattern;
                return matchCore(str, strIndex, pattern, patternIndex + 2)//*匹配0個,跳過
                        || matchCore(str, strIndex + 1, pattern, patternIndex + 2)//*匹配1個,跳過
                        || matchCore(str, strIndex + 1, pattern, patternIndex);//*匹配1個,再匹配str中的下一個
            } else {
                //直接跳過*(*匹配到0個)
                 //如果當前字符不匹配,則只能讓*表示當前字符出現0次,則str=str,pattern=pattern+2;
                return matchCore(str, strIndex, pattern, patternIndex + 2);
            }
        }

      //模式串下一字符不爲*
        if (pattern[patternIndex] == str[strIndex] || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
        }

        return false;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

54、表示數值的字符串

題目描述 
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串”+100”,”5e2”,”-123”,”3.1416”和”-1E-16”都表示數值。 但是”12e”,”1a3.14”,”1.2.3”,”+-5”和”12e+4.3”都不是。

思路1:正則表達式

public class Solution {
    public boolean isNumeric(char[] str) {
        String string = String.valueOf(str);
        return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

思路2:

public class Solution {
    boolean isNumeric(char[] s) {
        if (s.length == 0)
            return false;
        if ((s.length == 1) && (s[0] < '0' || s[0] > '9'))
            return false;
        if (s[0] == '+' || s[0] == '-') {
            if (s.length == 2 && (s[1] == '.'))
                return false;
        } else if ((s[0] < '0' || s[0] > '9') && s[0] != '.')
            return false;// 首位既不是符號也不是數字還不是小數點,當然是false
        int i = 1;
        while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
            i++;
        if (i < s.length && s[i] == '.') {
            i++;
            // if(i>=s.length) return false;
            while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
                i++;
        }
        if (i < s.length && (s[i] == 'e' || s[i] == 'E')) {
            i++;
            if ((i < s.length) && (s[i] == '+' || s[i] == '-')) {
                i++;
                if (i < s.length)
                    while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
                        i++;
                else
                    return false;
            } else if (i < s.length) {
                while ((i < s.length) && (s[i] >= '0' && s[i] <= '9'))
                    i++;
            } else
                return false;
        }
        if (i < s.length)
            return false;
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

55、字符流中第一個不重複的字符

題目描述 
請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符”go”時,第一個只出現一次的字符是”g”。當從該字符流中讀出前六個字符“google”時,第一個只出現一次的字符是”l”。

import java.util.*;
public class Solution {
    HashMap<Character, Integer> map=new HashMap();
    ArrayList<Character> list=new ArrayList<Character>();
    //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);
        }

        list.add(ch);
    }

    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {   char c='#';
        for(char key : list){
            if(map.get(key)==1){
                c=key;
                break;
            }
        }

        return c;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

56、鏈表中環的入口結點

題目描述 
一個鏈表中包含環,請找出該鏈表的環的入口結點。

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

    ListNode(int val) {
        this.val = val;
    }
}
 */
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        if (pHead == null || pHead.next == null)
            return null;
        // 先判斷是否有環
        ListNode n1 = pHead;// 走一步
        ListNode n2 = pHead;// 走兩步
        ListNode n = null;// 記錄n1,n2碰面的點
        while (n2 != null && n2.next != null) {
            n2 = n2.next.next;
            n1 = n1.next;
            if (n2 == n1) {
                n = n2;// 記錄碰頭節點
                break;
            }
        }
        // 求出環中節點數量
        int num = 0;
        ListNode temp = n;// n的鏡像
        do {
            temp = temp.next;
            num++;
        } while (temp != n);

        ListNode node1 = pHead;
        ListNode node2 = pHead;
        // node1先走num步,然後node1,node2同時走,碰頭的地方即入口節點
        for (int i = 0; i < num; i++) {
            node1 = node1.next;
        }
        int num1 = 0;

        while (node1 != node2) {
            node1 = node1.next;
            node2 = node2.next;
            num1++;
        }
        return node1;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

57、刪除鏈表中重複的結點

題目描述 
在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5

思路:重點是第一個也可能是重複的點,因此新建一個preNode節點保存前一個節點

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null)return null;

        ListNode preNode = null;
        ListNode node = pHead;
        while(node!=null){
            ListNode nextNode = node.next;
            boolean needDelete = false;//判斷相鄰兩個點是否相等
            if(nextNode!=null&&nextNode.val==node.val){
                needDelete = true;
            }
            if(!needDelete){
                preNode = node;
                node = node.next;
            }else{
                int value = node.val;
                ListNode toBeDel = node;
                while(toBeDel!=null&&toBeDel.val == value){
                    nextNode = toBeDel.next;
                    toBeDel = nextNode;
                    //此處不能少,找到第一個pHead,以後的preNode就不爲null了
                    if(preNode==null)
                        pHead = nextNode;
                    else
                        preNode.next = nextNode;
                    node = nextNode;
                }
            }
        }

        return pHead;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

58、二叉樹的下一個結點

題目描述 
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

思路:畫圖分析,考慮三種情況 
這裏寫圖片描述

/**
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode parent= null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    TreeLinkNode GetNext(TreeLinkNode node)
    {
        if(node==null) return null;
        if(node.right!=null){    //如果有右子樹,則找右子樹的最左節點
            node = node.right;
            while(node.left!=null) node = node.left;
            return node;
        }
        while(node.parent!=null){ //沒右子樹,則找第一個當前節點是父節點左孩子的節點
            if(node.parent.left==node) return node.parent;
            node = node.parent;
        }
        return null;   //退到了根節點仍沒找到,則返回null
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

59、對稱的二叉樹

題目描述 
請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其爲對稱的。

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

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {    
        return isSymmetrical(pRoot,pRoot);
    }

    //定義兩個遍歷,一個前序遍歷,一個是和前序遍歷相反的,先右後左的遍歷
    boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2) {
        if (pRoot1 == null && pRoot2 == null)
            return true;

        if (pRoot1 == null || pRoot2 == null)
            return false;

        if (pRoot1.val != pRoot2.val)
            return false;

        return isSymmetrical(pRoot1.left,pRoot2.right) && isSymmetrical(pRoot1.right, pRoot2.left);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

60、把二叉樹打印成多行

題目描述 
從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。

import java.util.*;

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

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        //使用隊列,先進先出
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;//start記錄本層打印了多少個,end記錄下一層要打印多少個
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);//添加本行打印的List裏
            start++;
            //每打印一個節點,就把此節點的下一層的左右節點加入隊列,並記錄下一層要打印的個數
            if(cur.left!=null){
                layer.add(cur.left);           
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            //本層打印完畢
            if(start == end){
                end = layer.size();
                start = 0;
                result.add(layerList);
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

61、按之字形順序打印二叉樹

題目描述 
請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其他行以此類推。

思路1:按上一題的方式,還是用隊列,用標記倒敘輸出,但是有缺點

import java.util.*;

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
 /*按層序遍歷分層打印的代碼,添加一段判斷用以倒序輸出即可*/
public class Solution {   
 /**
 * 缺點:將每層的數據存進ArrayList中,偶數層時進行reverse操作,但是在海量數據時,這樣效率太低了。
 */
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(pRoot == null){
            return result;
        }
        boolean leftToRight = true;
        Queue<TreeNode> layer = new LinkedList<TreeNode>();
        ArrayList<Integer> layerList = new ArrayList<Integer>();
        layer.add(pRoot);
        int start = 0, end = 1;
        while(!layer.isEmpty()){
            TreeNode cur = layer.remove();
            layerList.add(cur.val);
            start++;
            if(cur.left!=null){
                layer.add(cur.left);          
            }
            if(cur.right!=null){
                layer.add(cur.right);
            }
            if(start == end){
                end = layer.size();
                start = 0;             
                if(!leftToRight){
                    result.add(reverse(layerList));
                }else{
                    result.add(layerList);
                }
                leftToRight = !leftToRight;
                layerList = new ArrayList<Integer>();
            }
        }
        return result;
    }

    public ArrayList reverse(ArrayList<Integer> layerList) {    
        int length = layerList.size();
        ArrayList<Integer> reverseList = new ArrayList<Integer>();
        for(int i = length-1; i >= 0;i--){
            reverseList.add(layerList.get(i));
        }
        return reverseList;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

思路1改進:用LinkedList,可以雙向遍歷,

Iterator iter = iter = queue.iterator();//從前往後遍歷 
Iterator iter = queue.descendingIterator();//從後往前遍歷

import java.util.*;

public class Solution {
 /**
 * 思路:利用Java中的LinkedList的底層實現是雙向鏈表的特點。
 *     1)可用做隊列,實現樹的層次遍歷
 *     2)可雙向遍歷,奇數層時從前向後遍歷,偶數層時從後向前遍歷
 */
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
        if (pRoot == null) {
            return ret;
        }
        ArrayList<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.addLast(null);//層分隔符
        queue.addLast(pRoot);
        boolean leftToRight = true;

        while (queue.size() != 1) {
            TreeNode node = queue.removeFirst();//刪除第一個,返回刪除節點,若此節點不是分層的節點null,則添加左右子節點
            if (node == null) {//判斷刪除的節點是否爲null,以此來判斷是否到達分層隔符
                Iterator<TreeNode> iter = null;
                if (leftToRight) {
                    iter = queue.iterator();//從前往後遍歷
                } else {
                    iter = queue.descendingIterator();//從後往前遍歷
                }
                leftToRight = !leftToRight;
                while (iter.hasNext()) {
                    TreeNode temp = (TreeNode)iter.next();
                    list.add(temp.val);
                }
                ret.add(new ArrayList<Integer>(list));
                list.clear();
                queue.addLast(null);//添加層分隔符
                continue;//一定要continue
            }
            if (node.left != null) {
                queue.addLast(node.left);
            }
            if (node.right != null) {
                queue.addLast(node.right);
            }
        }

        return ret;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

思路2:用棧,先進後出,但是注意要用兩個棧,因爲如果用一個的話,本層的節點會壓在最底下,此節點的子節點會放在最上邊

import java.util.*;

public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
    ArrayList list=new ArrayList<ArrayList<Integer>>();
    if(pRoot==null)
        return list;
    Stack s1=new Stack();//從左到右輸出,所以添加時先加右,再加左
    Stack s2=new Stack();//從右到左輸出,所以添加時先加左,再加右
    s1.push(pRoot);
    ArrayList arr=new ArrayList<Integer>();      
       while(true){
           while(s1.size()!=0){
               TreeNode node=(TreeNode)s1.pop();
               arr.add(node.val);
               if(node.left!=null)
                   s2.push(node.left);
               if(node.right!=null)
                   s2.push(node.right);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();
           if(s1.size()==0&&s2.size()==0)
               break;
           while(s2.size()!=0){
                TreeNode node1=(TreeNode)s2.pop();
               arr.add(node1.val);
               if(node1.right!=null)
                   s1.push(node1.right);
               if(node1.left!=null)
                   s1.push(node1.left);
           }
           list.add(arr);
           arr=new ArrayList<Integer>();

           if(s1.size()==0&&s2.size()==0)
               break;        
       }

        return list;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

62、序列化二叉樹

題目描述 
請實現兩個函數,分別用來序列化和反序列化二叉樹

思路:通過前序遍歷的順序,但是修改了一下,子節點爲null的用#表示

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null; 
    public TreeNode(int val) {
        this.val = val; 
    } 
}
*/

public class Solution {
    public int index = -1;

     //序列化  
    public String Serialize(TreeNode root) {
        StringBuffer sb = new StringBuffer();
        if(root == null){
            sb.append("#,");
            return sb.toString();
        }
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
    }

    //反序列化 
    public TreeNode Deserialize(String str) {
        index++;//數組指數,每次移下一位
       int len = str.length();
        if(index >= len){
            return null;
        }
        String[] strr = str.split(",");
        TreeNode node = null;
        if(!strr[index].equals("#")){
            node = new TreeNode(Integer.valueOf(strr[index]));
            node.left = Deserialize(str);
            node.right = Deserialize(str);
        }        
        return node;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

63、二叉搜索樹的第K個節點

題目描述 
給定一顆二叉搜索樹,請找出其中的第k大的結點。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按結點數值大小順序第三個結點的值爲4。

思路:中序遍歷就是二叉搜索樹的排序,不用遞歸的程序

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        if(pRoot==null||k==0) return null;
        int t=0;
        while(pRoot!=null ||stack.size()>0){
            while(pRoot!=null){
                stack.push(pRoot);
                pRoot = pRoot.left;
            }
            if(stack.size()>0){
                pRoot= stack.pop();
                t++;
                if(t==k) return pRoot;
                pRoot= pRoot.right;
            }
        }
       return null;   
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

64、數據流中的中位數

題目描述 
如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從數據流中讀出偶數個數值,那麼中位數就是所有數值排序之後中間兩個數的平均值。

import java.util.*;
public class Solution{
    private Heap maxHeap = new Heap(Heap.isMaxHeap);
    private Heap minHeap = new Heap(Heap.isMinHeap);
    /**
     * 插入有兩種思路:
     * 1:直接插入大堆中,之後若兩堆尺寸之差大於1(也就是2),則從大堆中彈出堆頂元素並插入到小堆中
     * 若兩隊之差不大於1,則直接插入大堆中即可。
     * 2:奇數個數插入到大堆中,偶數個數插入到小堆中,
     * 但是 可能會出現當前待插入的數比小堆堆頂元素大,此時需要將元素先插入到小堆,然後將小堆堆頂元素彈出並插入到大堆中
     * 對於偶數時插入小堆的情況,一樣的道理。why?
     * 因爲要保證最大堆的元素要比最小堆的元素都要小。
     * @param num
     */
    public void Insert(Integer num) {
        //若總尺寸爲偶數,則插入大頂堆中
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            if(minHeap.size() != 0 && num > minHeap.peek()){
                minHeap.add(num);
                maxHeap.add(minHeap.pop());
            }else{
                maxHeap.add(num);
            }
        }else{
            if(maxHeap.size() != 0 && num < maxHeap.peek()){
                maxHeap.add(num);
                minHeap.add(maxHeap.pop());
            }else{
                minHeap.add(num);
            }
        }
    }
    public Double GetMedian() {
        double res = 0.0;
        if(((maxHeap.size() + minHeap.size()) & 1) == 0){
            res = (maxHeap.peek() + minHeap.peek()) / 2.0;
        }else{
            res = maxHeap.peek();
        }
        return res;
    }

    //堆類,可直接設置最大堆最小堆
    class Heap {
        public List<Integer> list = null;
        public static final boolean isMaxHeap = true;
        public static final boolean isMinHeap = false;
        private boolean flag = true;  //true表示最大堆,false表示最小堆
        public Heap(){
            this.list = new ArrayList<Integer>();
        }
        public Heap(boolean flag){
            this.list = new ArrayList<Integer>();
            this.flag = flag;
        }
        //獲取堆大小
        public int size(){
            return this.list.size();
        }
        //獲取堆頂元素
        public int peek(){
            if(list.size() == 0) return 0;
            return list.get(0);
        }
        //插入元素,從插入點開始向上調整堆
        public void add(int val){
            this.list.add(val);
            int i = list.size() - 1, index, parent, cur;
            while(i > 0){
                index = (i - 1) / 2;
                parent = list.get(index);
                cur = list.get(i);
                if(flag == true && parent < cur){
                    swap(index, i);
                }else if(flag == false && parent > cur){
                    swap(index, i);
                }
                i = index;
            }
        }
        /**
         * 將堆頂元素取出,並重新調整堆。
         * 1>取出堆頂元素
         * 2>將最後一個元素放到堆頂
         * 3>向下調整堆
         */
        public int pop(){
            if(list.size() == 0) return -1;
            int res = list.get(0);
            list.set(0,list.get(list.size() - 1));
            list.remove(list.size()-1);
            int len = list.size() - 1 , i = 0;
            int left , right;
            while(i < len){
                left = (i << 1) + 1;
                right= (i << 1) + 2;
                int maxIndex = i;
                if(flag == true){
                    if(left < len && list.get(left) > list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)> list.get(maxIndex)) maxIndex = right;
                }else{
                    if(left < len && list.get(left) < list.get(maxIndex)) maxIndex = left;
                    if(right< len && list.get(right)< list.get(maxIndex)) maxIndex = right;
                }
                if(maxIndex != i){
                    swap(maxIndex,i);
                    i = maxIndex;
                }else break;
            }
            return res;
        }
        //交換list中兩個位置的元素
        public void swap(int i, int j){
            int temp = list.get(i);
            list.set(i, list.get(j));
            list.set(j,temp);
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

思路2:

import java.util.ArrayList;
import java.util.Comparator;

/**
 * 數據流中的中位數
 *
 * @author 過路的守望
 *
 */
public class Solution {

    /*
     * 最大堆
     */
    private Heap maxHeap = new Heap(new Comparator<Integer>() {

        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    });
    /*
     * 最小堆
     */
    private Heap minHeap = new Heap(new Comparator<Integer>() {

        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });

    /*
     * 插入元素
     */
    public void Insert(Integer num) {
        /*
         * 如果最大堆中的元素個數大於等於最小堆中的元素個數,則新元素插入到最小堆中
         */
        if (maxHeap.getSize() >= minHeap.getSize()) {
            /*
             * 如果最小堆中未插入元素,則新元素直接插入
             */
            if (minHeap.getSize() == 0) {
                minHeap.add(num);
            }
            /*
             * 若最小堆中已存在元素,則將帶插入元素與最大堆中的堆頂元素相比較,若待插入元素較小,則將最大堆堆頂元素彈出後添加到最小堆中,
             * 最大堆中將待插入元素添加進去。
             */
            else if (num < maxHeap.peek()) {
                minHeap.add(maxHeap.pop());
                maxHeap.add(num);
            }
            /*
             * 待插入元素不小於最大堆堆頂元素
             */
            else {
                minHeap.add(num);
            }
        } else {
            /*
             * 如果最大堆中未插入元素,則新元素直接插入
             */
            if (maxHeap.getSize() == 0) {
                maxHeap.add(num);
            }
            /*
             * 若最大堆中已存在元素,則將帶插入元素與最小堆中的堆頂元素相比較,若待插入元素較大,則將最小堆堆頂元素彈出後添加到最大堆中,
             * 最小堆中將待插入元素添加進去。
             */
            else if (num > minHeap.peek()) {
                maxHeap.add(minHeap.pop());
                minHeap.add(num);
            }
            /*
             * 待插入元素不大於最小堆堆頂元素
             */
            else {
                maxHeap.add(num);
            }
        }
    }

    /*
     * 得到中間值
     */
    public Double GetMedian() {
        /*
         * 判斷數據數目奇偶
         */
        int size = maxHeap.getSize() + minHeap.getSize();
        if ((size & 1) == 1) {
            return (double) minHeap.peek();
        }
        return (maxHeap.peek() + minHeap.peek()) / 2.0;
    }

}

/**
 * 數據結構-堆
 *
 * @author 過路的守望
 *
 */
class Heap {

    /*
     * 比較器
     */
    private Comparator<Integer> comparator;
    private ArrayList<Integer> list;

    public Heap() {
        list = new ArrayList<Integer>();
    }

    /*
     * 構造器傳入比較器
     */
    public Heap(Comparator<Integer> comparator) {
        this();
        this.comparator = comparator;
    }

    /*
     * 彈出堆頂元素
     */
    public int pop() {
        int data = list.get(0);
        list.set(0, list.remove(list.size() - 1));
        percolateDown();
        return data;
    }

    /*
     * 返回堆頂元素
     */
    public int peek() {
        return list.get(0);
    }

    /*
     * 添加元素
     */
    public void add(int element) {
        list.add(element);
        percolateUp();
        return;
    }

    /*
     * 堆中對象個數
     */
    public int getSize() {
        return list.size();
    }

    /*
     * 下濾操作
     */
    private void percolateDown() {
        int size = list.size();
        /*
         * 從下標爲0的節點開始下濾
         */
        int i = 0;
        /*
         * temp保存最大堆或最小堆的堆頂值
         */
        int temp = list.get(0);
        int leftChild = getLeftChild(i);
        while (leftChild < size) {
            /*
             * 得到左右兒子中較大的下標
             */
            if (leftChild < size - 1
                    && comparator.compare(list.get(leftChild),
                            list.get(leftChild + 1)) < 0) {
                leftChild++;
            }
            /*
             * 若兒子大於父親,就把 兒子的值賦給父親
             */
            if (comparator.compare(temp, list.get(leftChild)) < 0) {
                list.set(i, list.get(leftChild));
                i = leftChild;
                leftChild = getLeftChild(i);
                continue;
            } else {
                break;
            }
        }
        /*
         * 下濾完成,找到temp所在下標
         */
        list.set(i, temp);
    }

    /*
     * 上濾操作
     */
    private void percolateUp() {
        /*
         * 從堆中最後一個元素開始上濾
         */
        int i = list.size() - 1;
        int temp = list.get(i);
        int parent = getParent(i);
        while (parent >= 0) {
            /*
             * 若父親小於兒子,則把父親的值賦給兒子
             */
            if (comparator.compare(temp, list.get(parent)) > 0) {
                list.set(i, list.get(parent));
                i = parent;
                parent = getParent(parent);
                continue;
            } else {
                break;
            }
        }
        /*
         * 上濾完成,找到temp所在下標
         */
        list.set(i, temp);
    }

    /*
     * 左兒子下標
     */
    private int getLeftChild(int i) {
        return 2 * i + 1;
    }

    /*
     * 父親下標
     */
    private int getParent(int i) {
        return (int) Math.floor((i - 1) / 2.0);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244

65、滑動窗口的最大值

題目描述 
給定一個數組和滑動窗口的大小,找出所有滑動窗口裏數值的最大值。例如,如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有以下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

import java.util.*;
/**
 * 題目:滑動窗口的最大值
 * 思路:滑動窗口應當是隊列,但爲了得到滑動窗口的最大值,隊列序可以從兩端刪除元素,因此使用雙端隊列。
 *     原則:
 *     對新來的元素k,將其與雙端隊列中的元素相比較
 *     1)前面比k小的,直接移出隊列(因爲不再可能成爲後面滑動窗口的最大值了!),
 *     2)前面比k大的X,比較兩者下標,判斷X是否已不在窗口之內,不在了,直接移出隊列
 *     隊列的第一個元素是滑動窗口中的最大值
 */
public class Solution {

    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {

        ArrayList<Integer> ret = new ArrayList<>();
        if (num == null) {
            return ret;
        }
        if (num.length < size || size < 1) {
            return ret;
        }
        // 用來保存可能是滑動窗口最大值的數字的下標 
        LinkedList<Integer> indexDeque = new LinkedList<>();
        for (int i = 0; i < size - 1; i++) {
            // 如果已有數字小於待存入的數據,
                // 這些數字已經不可能是滑動窗口的最大值
                // 因此它們將會依次地從隊尾刪除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
        }

        for (int i = size - 1; i < num.length; i++) {
            // 如果已有數字小於待存入的數據,
                // 這些數字已經不可能是滑動窗口的最大值
                // 因此它們將會依次地從隊尾刪除
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
            if (i - indexDeque.getFirst() + 1 > size) {
                indexDeque.removeFirst();
            }
            ret.add(num[indexDeque.getFirst()]);
        }
        return ret;
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章