刷題記錄之牛客網劍指Offer66題Java版(詳細註釋)

目錄

01.二維數組中的查找

02.替換空格

03.從尾到頭打印鏈表

04.根據前序和中序重建二叉樹

05.用兩個棧實現隊列

06.旋轉數組的最小數字

07.斐波那契數列

08.跳臺階

09.升級版跳臺階

10.矩形覆蓋

11.二進制中1的個數

12.數值的整數次方

13.調整數組順序使奇數位於偶數前面

14.鏈表中倒數第k個節點

15.反轉鏈表

16.合併兩個排序的鏈表

17.樹的子結構

18.二叉樹的鏡像

19.順時針打印矩陣

20.包含min函數的棧

21.棧的壓入、彈出序列

22.從上至下逐層打印二叉樹

23.二叉搜索樹的後序遍歷序列

24.二叉樹中和爲某一值的路徑

25.複雜鏈表的複製

26.二叉搜索樹與雙向鏈表

27.字符串的排列

28.數組中出現次數超過一半的數字

29.最小的k個數

30.連續子數組的最大和

31.整數中1出現的次數(從1到n整數中1出現的次數)

32.把數組排成最小的數

33.醜數

34.第一個只出現一次的字符

35.數組中的逆序對

36.兩個鏈表的第一個公共節點

37.數字在排序數組中出現的次數

38.二叉樹的深度

39.平衡二叉樹

40.數組中只出現一次的數字

41.和爲S的連續正數序列

42.和爲S的兩個數字

43.左旋轉字符串

44.旋轉單詞順序列

45.撲克牌順子

46.孩子們的遊戲(圓圈中最後剩下的數)

47.求1+2+3+...+n

48.不用加減乘除做加法

49.把字符串轉換成整數

50.數組中重複的數字

51.構建乘積數組

52.正則表達式匹配

53.表示數值的字符串

54.字符流中第一個不重複的字符

55.鏈表中環的入口節點

56.刪除鏈表中重複的節點

57.二叉樹的下一個節點

58.對稱的二叉樹

59.按之字形打印二叉樹

60.把二叉樹打印成多行

61.序列化二叉樹

62.二叉搜索樹的第k個節點

63.數據流的中位數

64.滑動窗口的最大值

65.矩陣中的路徑

66.機器人的運動範圍

END


01.二維數組中的查找

題目描述

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

public class Solution {
    public boolean Find(int target, int [][] array) {
        //從右上角到左下角遍歷
        int row = 0, col = array[0].length - 1;
        while(row < array.length && col >= 0) {
            if(target == array[row][col])
                return true;
            //target小於當前值,說明下方和右方整個塊全部作廢,因此左移1位
            else if(target < array[row][col])
                col--;
            else
                row++;
        }
        return false;
    }
}

02.替換空格

題目描述

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

public class Solution {
    public String replaceSpace(StringBuffer str) {
        String s = str.toString();
        String res = "";
        for(int i = 0; i < s.length(); i++) {
            if(s.charAt(i) == ' ')
                res += "%20";
            else
                res += s.charAt(i);
        }
        return res;
    }
}

03.從尾到頭打印鏈表

題目描述

輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        ListNode head = reverseList(listNode);
        while(head != null) {
            arrayList.add(head.val);
            head = head.next;
        }
        return arrayList;
    }
    //鏈表轉置函數
    public static ListNode reverseList(ListNode listNode) {
        ListNode tail = null;
        while(listNode != null){
            ListNode p = listNode.next;  //儲存next
            listNode.next = tail;
            tail = listNode;  //更新tail
            listNode = p;
        }
        return tail;
    }
}

04.根據前序和中序重建二叉樹

題目描述

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
    }
    //用下標獲取每個子樹的前序和中序序列
    public TreeNode reConstructBinaryTree(int [] pre, int preStart, int preEnd, 
                                          int [] in, int inStart, int inEnd) {
        if(preStart > preEnd || inStart > inEnd)  //序列爲空,返回null
            return null;
        TreeNode res = new TreeNode(pre[preStart]);  //前序第1個值是根節點
        if(preStart == preEnd)  //只有一個節點
            return res;
        int leftCount = 0;  //左子樹的大小
        for(int i = inStart; in[i] != pre[preStart]; i++)
            leftCount++;
        res.left = reConstructBinaryTree(pre, preStart + 1, preStart + leftCount,
                                         in, inStart, inStart + leftCount - 1);
        res.right = reConstructBinaryTree(pre, preStart + leftCount + 1, preEnd,
                                          in, inStart + leftCount + 1, inEnd);
        return res;
    }
}

05.用兩個棧實現隊列

題目描述

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

import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack2.isEmpty()) {  //stack2爲空時,stack1中要全部移入stack2保證順序
            while(!stack1.isEmpty())
                stack2.push(stack1.pop());
        }
        return stack2.pop();    
    }
}

06.旋轉數組的最小數字

題目描述

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

public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array.length == 0)
            return 0;
        int start = 0, end = array.length - 1;
        //二分查找
        while(start < end) {
            int mid = (start + end) / 2;
            if(array[mid] < array[start])  //最小值在左半邊(要包括mid)
                end = mid;
            else if(array[mid] > array[end])  //最小值在右半邊(不包括mid)
                start = mid + 1;
            else  //即 array[start] <= array[mid] <= array[end]
                return array[start];
                
        }
        return array[start];
    }
}

07.斐波那契數列

題目描述

大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。

n<=39

public class Solution {
    public int Fibonacci(int n) {
        int a = 0, b = 1;
        for(int i = 0; i < n / 2; i++) {
            a += b;
            b += a;
        }
        return n % 2 == 0 ? a : b;
    }
}

08.跳臺階

題目描述

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。

public class Solution {
    public int JumpFloor(int target) {
        //還是斐波那契數列
        int a = 1, b = 2;
        for(int i = 0; i < (target - 1) / 2; i++) {
            a += b;
            b += a;
        }
        return target % 2 == 1 ? a : b;
    }
}

09.升級版跳臺階

題目描述

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

public class Solution {
    public int JumpFloorII(int target) {
        /*
        a[1] = 1, a[2] = 2
        根據最後1步跳的距離區分,是1到target-1時,跳法數分別對應a[target-1]到a[1]
        最後再加1(就是最後1步跳整個target長)
        a[n] = 1+a[n-1]+a[n-2]+...+a[1] = 1+S[n-1]
        S[n] = a[n]+S[n-1] = 2*S[n-1]+1
        S[n]+1 爲等比數列
        最後得通項公式爲 a[n] = 2^(n-1)
        */
        return (int)Math.pow(2.0, (double)(target - 1));
    }
}

10.矩形覆蓋

題目描述

我們可以用2*1的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

public class Solution {
    public int RectCover(int target) {
        //又是斐波那契數列
        if(target == 0)
            return 0;
        int a = 1, b = 2;
        for(int i = 0; i < (target - 1) / 2; i++) {
            a += b;
            b += a;
        }
        return target % 2 == 1 ? a : b;
    }
}

11.二進制中1的個數

題目描述

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

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        while(n != 0){
            count++;
            // n&(n-1)可以消掉最右邊的一個1
            n = n & (n - 1);
         }
        return count;
    }
}

12.數值的整數次方

題目描述

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

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent == 0)
            return 1.0;
        boolean isNegExponent = false;
        //注意判斷負指數
        if(exponent < 0) {
            isNegExponent = true;
            exponent = -exponent;
        }
        //直接遞歸求根號值就ok
        double sqrtRes = Power(base, exponent / 2);
        double res = 1.0;
        if(exponent % 2 == 0)
            res = sqrtRes * sqrtRes;
        else
            res = sqrtRes * sqrtRes * base;
        if(isNegExponent)
            return 1 / res;
        else
            return res;
    }
}

13.調整數組順序使奇數位於偶數前面

題目描述

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

public class Solution {
    public void reOrderArray(int [] array) {
        // 和插入排序相同
        int p = 0;  // 插入位置
        for (int i = 0; i < array.length; i++) {
            // 只需對奇數向前插入,偶數不用操作
            if (array[i] % 2 != 0) {
                int temp = array[i];
                for (int j = i - 1; j >= p; j--) {
                    array[j + 1] = array[j];
                }
                array[p] = temp;
                p++;
            }
        }
    }
}

14.鏈表中倒數第k個節點

題目描述

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

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if (head == null || k <= 0) {
            return null;
        }
        ListNode p1 = head, p2 = head;
        int i = 0;
        while (p2 != null && i < k) {
            p2 = p2.next;
            i++;
        }
        // 長度不夠返回null
        if (i < k) {
            return null;
        }
        while (p2 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p1;
    }
}

15.反轉鏈表

題目描述

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

public class Solution {
    //鏈表轉置函數
    public static ListNode ReverseList(ListNode head) {
        ListNode tail = null;
        while(head != null){
            ListNode p = head.next;  //儲存next
            head.next = tail;
            tail = head;  //更新tail
            head = p;
        }
        return tail;
    }
}

16.合併兩個排序的鏈表

題目描述

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

先上非遞歸:

public class Solution {
    public ListNode Merge(ListNode list1, ListNode list2) {
        // 爲了節約空間複雜度,這裏不用遞歸
        if (list1 == null) {
            return list2;
        } else if (list2 == null) {
            return list1;
        }
        // 讓list1指向更小表頭,然後把list2插入到list1
        if (list2.val < list1.val) {
            ListNode temp = list1;
            list1 = list2;
            list2 = temp;
        }
        // head記錄更小表頭,作爲最後返回值
        ListNode head = list1;
        // list1最後還要用到,所以指向最後一個節點爲止,不要到null
        while (list1.next != null && list2 != null) {
            // 這裏保證始終有 list1.val <= list2.val
            if (list1.next.val <= list2.val) {
                list1 = list1.next;
                continue;
            }
            // 否則把當前list2節點插入
            ListNode temp = list2.next;
            list2.next = list1.next;
            list1.next = list2;
            list2 = temp;
        }
        // list2剩餘
        if (list2 != null) {
            list1.next = list2;
        }
        return head;
    }

}

遞歸比較簡單: 

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

17.樹的子結構

題目描述

輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        //前序遍歷
        //這道題的意思是值相同,不是指同一節點,且可以只是中間部分的子結構
        if(root2 == null || root1 == null)  //由題意都不爲null纔行
            return false;
        if(HasSubtreeFromRoot(root1, root2))  //先判斷根節點
            return true;
        else  //遞歸左右
            return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    }
    public boolean HasSubtreeFromRoot(TreeNode root1,TreeNode root2) {
        //root1可能包含root2,從根節點開始比較,只要root2能遍歷完即可
        //以下保證輸入root2非空
        if(root1 == null)
            return false;
        else if(root1.val != root2.val)
            return false;
        boolean res = true;
        if(root2.left != null)  //非空就要遞歸判斷
            res = res && HasSubtreeFromRoot(root1.left, root2.left);
        if(root2.right != null)
            res = res && HasSubtreeFromRoot(root1.right, root2.right);
        return res;
    }
}

18.二叉樹的鏡像

題目描述

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。

輸入描述

二叉樹的鏡像定義:源二叉樹 
            8
           /  \
          6   10
         / \     / \
        5  7 9 11
        鏡像二叉樹
            8
           /  \
          10   6
          / \     / \
        11 9 7  5

public class Solution {
    public void Mirror(TreeNode root) {
        if(root != null) {
            //先交換左右子樹,再分別置鏡像
            TreeNode temp = root.left;
            root.left = root.right;
            root.right = temp;
            Mirror(root.left);
            Mirror(root.right);
        }
    }
}

19.順時針打印矩陣

題目描述

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

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(matrix.length == 0)
            return res;
        int step = 0;  //第幾圈
        int i = step, j = step;
        //總共的圈數,由matrix的長和寬中較小的那個決定
        int totalLoops = (Math.min(matrix.length, matrix[0].length) + 1) / 2;
        while(step < totalLoops) {
            while(j < matrix[0].length - step)  //向右到最右,否則一條線構不成圈時會漏掉
                res.add(matrix[i][j++]);
            j--;  //第一個方向一定會執行,j回位
            i++;  //行數下移1
            while(i < matrix.length - step)  //向下,同樣要到最下
                res.add(matrix[i++][j]);
            //前兩個方向結束就可以判斷退出
            if(i == step + 1 || j == step)  //分別是一行和一列的情況,退出
                break;
            i--;  //i回位
            j--;  //j左移1
            while(j > step)  //向左
                res.add(matrix[i][j--]);
            while(i > step)  //向上
                res.add(matrix[i--][j]);
            step++;
            i = step;
            j = step;
        }
        return res;
    }
}

20.包含min函數的棧

題目描述

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))。

import java.util.Stack;
public class Solution {
    long min;
    Stack<Long> stack = new Stack<Long>();
    public void push(int node) {
        if(stack.isEmpty()) {
            stack.push((long)node);
            min = (long)node;
        }
        else {
            stack.push((long)node - min);  //入棧的是node減去(舊的)min
            if((long)node < min)  //更新min
                min = (long)(node);
        }
    }
    
    public void pop() {
        if(stack.isEmpty())
            return;
        long pop = stack.pop();
        if(pop < 0)  //出棧的是更新了min的node,min要還原
            min = min - pop;  //右邊min是node真實值,而pop是node真實值減去(舊的)min
    }
    
    public int top() {
        long top = stack.peek();
        if(top >= 0)
            return (int)(top + min);
        else  //top小於0時,node減去的是舊的min,當前min是node真實值
            return (int)min;
    }
    
    public int min() {
        return (int)min;
    }
}

21.棧的壓入、彈出序列

題目描述

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

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length != popA.length)
            return false;
        Stack<Integer> stack = new Stack<Integer>();
        int i = 0;
        //對每個出棧序列元素執行檢測,匹配上則pop(),否則返回false
        for(int j = 0; j < popA.length; j++) {
            //一直入棧直到匹配成功
            while(stack.empty() || stack.peek() != popA[j]) {
                if(i < pushA.length)  //i合法,繼續入棧
                    stack.push(pushA[i++]);
                else  //i越界,匹配失敗
                    return false;
            }
            stack.pop();  //匹配成功後出棧
        }
        return true;     
    }
}

22.從上至下逐層打印二叉樹

題目描述

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

public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        //層次遍歷即可
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(root == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            res.add(cur.val);
            if(cur.left != null)
                queue.offer(cur.left);
            if(cur.right != null)
                queue.offer(cur.right);
        }
        return res;
    }
}

23.二叉搜索樹的後序遍歷序列

題目描述

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

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        //後序遍歷是左,右,中
        //起始就是空數組時,不算是搜索樹,返回false
        if(sequence.length == 0)
            return false;
        return VerifySquenceOfBST(sequence, 0, sequence.length - 1);
    }
    public boolean VerifySquenceOfBST(int [] sequence, int start, int end) {
        //對於子樹,start > end視作空子樹,返回true
        if(start >= end)
            return true;
        int i = start;
        int leftEnd = start;
        //左子樹全部比根節點小
        while(sequence[i] < sequence[end])
            i++;
        leftEnd = i - 1;
        //右子樹全部比根節點大
        while(sequence[i] > sequence[end])
            i++;
        //到達根節點
        if(i == end)
            return VerifySquenceOfBST(sequence, start, leftEnd) && VerifySquenceOfBST(sequence, leftEnd + 1, end - 1);
        //沒有到達,返回false
        else
            return false;
    }
}

24.二叉樹中和爲某一值的路徑

題目描述

輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)

public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<ArrayList<Integer>>();
        ArrayList<Integer> list = new ArrayList<Integer>();
        preOrder(root, target, res, list);
        return res;
    }
    //前序遍歷
    public void preOrder(TreeNode cur, int target, ArrayList<ArrayList<Integer>> res, ArrayList<Integer> list) {
        if(cur != null) {
            list.add(cur.val);  //加入list
            if(cur.left == null && cur.right == null && cur.val == target)  //滿足條件的葉節點
                res.add(new ArrayList(list));  //要先複製list再加入res纔對
            else {
                preOrder(cur.left, target - cur.val, res, list);
                preOrder(cur.right, target - cur.val, res, list);
            }
            list.remove(list.size() - 1);  //回退時需要移除最後一個元素
        }
    }
}

25.複雜鏈表的複製

題目描述

輸入一個複雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)

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

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode head) {
        if(head == null) {
            return null;
        }
        //頭節點非空,先複製頭節點爲res
        RandomListNode res = new RandomListNode(head.label);
        res.next = head.next;
        head.next = res;
        //第一輪複製,先不考慮隨機指針,每個複製的節點插入到原節點之後
        for(RandomListNode p = res.next; p != null;) {
            RandomListNode p1 = new RandomListNode(p.label);
            p1.next = p.next;
            p.next = p1;
            p = p1.next;
        }
        //第二輪,連接隨機指針,p1.random = p.random.next;
        for(RandomListNode p = head; p != null;) {
            RandomListNode p1 = p.next;
            if(p.random != null) {
                p1.random = p.random.next;
            }
            p = p1.next;
        }
        //最後取出複製的鏈表,並將原鏈表還原
        head.next = res.next;
        for(RandomListNode p = res.next, p1 = res; p != null;) {
            p1.next = p.next;
            p1 = p1.next;
            p.next = p1.next;
            p = p.next;
        }
        return res;
    }
}

26.二叉搜索樹與雙向鏈表

題目描述

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

public class Solution {
    //全局變量保存鏈表頭和尾
    TreeNode head = null;
    TreeNode tail = null;

    public TreeNode Convert(TreeNode pRootOfTree) {
        inOrder(pRootOfTree);
        return head;
    }
    //中序遍歷
    public void inOrder(TreeNode root) {
        if(root != null) {
            inOrder(root.left);
            if(head == null) {
                head = root;
                tail = root;
            }
            else {
                tail.right = root;
                root.left = tail;
                tail = root;
            }
            inOrder(root.right);
        }
    }
}

27.字符串的排列

題目描述

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

輸入描述:

輸入一個字符串,長度不超過9(可能有字符重複),字符只包括大小寫字母。

import java.util.ArrayList;
import java.util.Collections;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> res = new ArrayList<String>();
        if(str.length() == 0)
            return res;
        permute(str.toCharArray(), 0, res);  //生成res
        Collections.sort(res);
        return res;
    }
    public void permute(char[] ch, int i, ArrayList<String> list) {
        if(i == ch.length - 1) {  //遞歸出口
            String cur = new String(ch);
            if(!list.contains(cur))  //如果沒有重複就存入當前字符串
                list.add(cur);
        }
        else {
            for(int j = i; j < ch.length; j++) {  //逐個交換字母,遞歸,再換回
                swap(ch, i, j);
                permute(ch, i + 1, list);
                swap(ch, i, j);
            }
        }
    }
    public void swap(char[] ch, int i, int j) {  //交換兩個字母順序
        char temp = ch[i];
        ch[i] = ch[j];
        ch[j] = temp;
    }
}

28.數組中出現次數超過一半的數字

題目描述

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

public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        //這道題要利用數組的特殊性質,不需要HashMap
        if(array.length == 0)
            return 0;
        int res = array[0];
        int count = 1;
        //經過第一次循環假如有超過一半的元素一定會留下來
        for(int i = 1; i < array.length; i++) {
            if(count == 0) {  //count爲0,更新res
                res = array[i];
                count = 1;
            }
            else if(array[i] == res)
                count++;
            else
                count--;
        }
        //第二次循環驗證留下來的是否是超過一半次的元素
        count = 0;
        for(int i = 0; i < array.length; i++) {
            if(array[i] == res)
                count++;
            if(count > array.length / 2)
                return res;
        }
        return 0;
    }
}

29.最小的k個數

題目描述

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

import java.util.ArrayList;
import java.util.Random;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input.length == 0 || k > input.length) {
            return res;
        }
        GetLeastNumbers_Solution(input, k, 0, input.length - 1, res);
        return res;
    }
    private void GetLeastNumbers_Solution(int [] nums, int k, int start, int end,
                                                       ArrayList<Integer> res) {
        // 快排法求topK
        if (start > end || k == 0) {
            return;
        }
        if (k == end - start + 1) {
            for (int i = start; i <= end; i++) {
                res.add(nums[i]);
            }
            return;
        }
        int p = partition(nums, start, end);
        // p之前多於k個時無法確定,k不變,end左移
        if (p - start > k) {
            GetLeastNumbers_Solution(nums, k, start, p - 1, res);
        } else {
            // 否則p之前都可以加入res
            for (int i = start; i < p; i++) {
                res.add(nums[i]);
            }
            GetLeastNumbers_Solution(nums, k - (p - start), p, end, res);
        }
    }
    // 二路快排
    private int partition(int[] nums, int i, int j) {
        // 隨機洗牌
        int rand = new Random().nextInt(j - i + 1);
        swap(nums, i, i + rand);

        int pivot = nums[i];
        while (i < j) {
            while (i < j && nums[j] >= pivot) {
                j--;
            }
            nums[i] = nums[j];
            while (i < j && nums[i] <= pivot) {
                i++;
            }
            nums[j] = nums[i];
        }
        nums[i] = pivot;
        return i;
    }
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

30.連續子數組的最大和

題目描述

HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全爲正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和爲8(從第0個開始,到第3個爲止)。給一個數組,返回它的最大連續子序列的和,你會不會被他忽悠住?(子向量的長度至少是1)

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int curSum = array[0];  //curSum的定義是包含當位的值
        int maxSum = curSum;
        for(int i = 1; i < array.length; i++){
            if(curSum <= 0)  //舊的和非正數,則拋棄
                curSum = array[i];
            else
                curSum = curSum + array[i];
            maxSum = Math.max(maxSum, curSum);  //更新maxSum
        }
        return maxSum;
    }
}

31.整數中1出現的次數(從1到n整數中1出現的次數)

題目描述

求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        //計算逐個位的1的次數並求和
        int count = 0;
        int weight = 1;
        int r = 0;  //餘數,用來計算當位是1時這個1帶來的個數
        while(weight <= n) {
            r = n % weight;
            if((n / weight) % 10 > 1)  //當位大於1時增加weight個(如百位含100~199共100個1)
                count += weight;
            else if((n / weight) % 10 == 1)  //當位是1時增加r+1個(如百位含100~109共10個1)
                count += r + 1;
            count += (n / weight / 10) * weight;  //高位帶來的當位1的個數,
                                       //如00100到23199,除去額外的199,百位增加共23*100個1
                                       //千萬不能化簡算式,否則最高位時結果不同!
            weight *= 10;
        }
        return count;
    }
}

32.把數組排成最小的數

題目描述

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

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        //普通排序即可,如果交換位置後拼成的數更小,就交換
        for(int i = 0; i < numbers.length - 1; i++){
            for(int j = i + 1; j < numbers.length; j++){
                int a = Integer.valueOf(numbers[i]+""+numbers[j]);
                int b = Integer.valueOf(numbers[j]+""+numbers[i]);
                if(a > b){
                    int temp = numbers[i];
                    numbers[i] = numbers[j];
                    numbers[j] = temp;
                }
            }
        }
        String str = "";
        for(int i = 0; i < numbers.length; i++)
            str += numbers[i];
        return str;
    }
}

33.醜數

題目描述

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

public class Solution {
    /*醜數可以分爲三個序列
    (1) 1×2, 2×2, 3×2, 4×2, 5×2, …
    (2) 1×3, 2×3, 3×3, 4×3, 5×3, …
    (3) 1×5, 2×5, 3×5, 4×5, 5×5, …
    每個序列都是醜數序列本身 * 2, 3, 5,每次從這三個序列選最小就可以獲取醜數序列;
    之後更新(包括重複值也要更新纔行)*/
    public int GetUglyNumber_Solution(int n) {
        if(n == 0)
            return 0;
        int[] ugly = new int[n];
        ugly[0] = 1;
        int index2 = 0, index3 = 0, index5 = 0;
        int factor2 = 2, factor3 = 3, factor5 = 5;
        for(int i = 1; i < n; i++) {
            int min = Math.min(Math.min(factor2, factor3), factor5);  //選最小
            ugly[i] = min;
            if(factor2 == min)  //因爲可能有重複值,一定要3個都判斷並更新,不可以用else
                factor2 = 2 * ugly[++index2];
            if(factor3 == min)
                factor3 = 3 * ugly[++index3];
            if(factor5 == min)
                factor5 = 5 * ugly[++index5];
        }
        return ugly[n - 1];
    }
}

34.第一個只出現一次的字符

題目描述

在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫).

import java.util.HashMap;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        //直接上HashMap計數即可
        HashMap<Character, Integer> map = new HashMap<Character, Integer>();
        int res = -1;
        for(int i = 0; i < str.length(); i++)
            map.put(str.charAt(i), map.getOrDefault(str.charAt(i), 0) + 1);
        for(int i = 0; i < str.length(); i++)
            if(map.get(str.charAt(i)) == 1) {
                res = i;
                break;
            }
        return res;
    }
}

35.數組中的逆序對

題目描述

在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007

輸入描述:

題目保證輸入的數組中沒有的相同的數字

數據範圍:

對於%50的數據,size<=10^4

對於%75的數據,size<=10^5

對於%100的數據,size<=2*10^5

public class Solution {
    public int InversePairs(int [] array) {
        //歸併排序並且統計逆序數
        long[] count = new long[1];
        mergeSort(array, 0, array.length - 1, count);
        return (int)count[0];
    }
    //歸併排序
    public void mergeSort(int[] array, int start, int end, long[] count) {
        if(start >= end)
            return;
        int mid = start + (end - start) / 2;  //預防int溢出的寫法
        mergeSort(array, start, mid, count);
        mergeSort(array, mid + 1, end, count);
        merge(array, start, mid, end, count);
    }
    public void merge(int[] array, int start, int mid, int end, long[] count) {
        //對start到mid,以及mid+1到end兩部分進行合併
        int[] temp = new int[end - start + 1];
        int i = start, j = mid + 1, k = 0;
        while(i <= mid && j <= end) {
            if(array[i] > array[j]) {  //出現逆序,更新count
                temp[k++] = array[j++];
                count[0] += mid - i + 1;  //右側array[j]左移越過的左側個數即增加的逆序數
                count[0] %= 1000000007;  //預防數值過大溢出
            }
            else
                temp[k++] = array[i++];
        }
        while(i <= mid)
            temp[k++] = array[i++];
        while(j <= end)
            temp[k++] = array[j++];
        for (k = 0; k < temp.length; k++)  //將合併後數組移回原數組
            array[start + k] = temp[k];
    }
}

36.兩個鏈表的第一個公共節點

題目描述

輸入兩個鏈表,找出它們的第一個公共結點。

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        int length1 = 0, length2 = 0;
        ListNode l1, l2;
        //先獲取兩個鏈表長度
        for(l1 = pHead1; l1 != null; l1 = l1.next)
            length1++;
        for(l2 = pHead2; l2 != null; l2 = l2.next)
            length2++;
        //讓l1指向較長一個鏈表
        if(length1 < length2) {
            l1 = pHead2;
            l2 = pHead1;
        }
        else {
            l1 = pHead1;
            l2 = pHead2;
        }
        for(int i = 0; i < Math.abs(length1 - length2); i++)  //l1先移動長度差
            l1 = l1.next;
        while(l1 != l2) {
            l1 = l1.next;
            l2 = l2.next;
        }
        return l1;
    }
}

37.數字在排序數組中出現的次數

題目描述

統計一個數字在排序數組中出現的次數。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        // 利用二分查找的變種方法,找到第1個k和最後1個k,計算位置跨度
        int first = binarySearchFirst(array, k);
        if (first == -1) {
            return 0;
        }
        return binarySearchLast(array, k) - first + 1;
    }
    // 二分查找第1個k
    public int binarySearchFirst(int [] array , int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = (start + end) / 2;
            if(array[mid] >= k)
                end = mid - 1;
            else
                start = mid + 1;
        }
        if (start < array.length && array[start] == k) {
            return start;
        }
        return -1;
    }
    // 二分查找最後1個k
    public int binarySearchLast(int [] array , int k) {
        int start = 0, end = array.length - 1;
        while(start <= end) {
            int mid = (start + end) / 2;
            if(array[mid] <= k)
                start = mid + 1;
            else
                end = mid - 1;
        }
        if (end >= 0 && array[end] == k) {
            return end;
        }
        return -1;
    }
}

38.二叉樹的深度

題目描述

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

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root == null)
            return 0;
        else {
            return 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
        }
    }
}

39.平衡二叉樹

題目描述

輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(root == null)
            return true;
        //比較左右子樹深度即可
        int leftDepth = getDepth(root.left);
        int rightDepth = getDepth(root.right);
        if(Math.abs(leftDepth - rightDepth) > 1)
            return false;
        else
            return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }
    public int getDepth(TreeNode root) {
        if(root == null)
            return 0;
        else
            return 1 + Math.max(getDepth(root.left), getDepth(root.right));
    }
}

40.數組中只出現一次的數字

題目描述

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

//num1,num2分別爲長度爲1的數組。傳出參數
//將num1[0],num2[0]設置爲返回結果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int bitResult = 0;
        //異或消去重複的數,最後得到bitResult是這兩個數的異或
        for(int i = 0; i < array.length; i++) {
            bitResult ^= array[i];
        }
        int index = 0;
        //從低位到高位找到這兩個數第一個異或後爲1,也就是第一個不同的數位
        while(index < 32 && ((bitResult & 1) == 0)){
            index++;
            bitResult >>= 1;
        }
        //最後求一遍異或,根據不同位爲1還是0分開,即可分別得到兩個數
        for(int i = 0; i < array.length; i++) {
            if(((array[i] >> index) & 1) == 1)
                num1[0] ^= array[i];
            else
                num2[0] ^= array[i];
        }
    }
}

41.和爲S的連續正數序列

題目描述

小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的 正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!

輸出描述:

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

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer> > res = new ArrayList<ArrayList<Integer> >();
        if(sum <= 2)
            return res;
        int n = 2;  //連續正數的個數爲n
        int mid = sum / n;
        int first = mid + 1 - n / 2;  //n爲偶數的first計算公式,奇數沒有+1
        boolean nIsOdd = false;
        while(first > 0) {  
            //n爲奇數或偶數分別處理
            if(nIsOdd && sum % n == 0) {  //n爲奇數時要能除開
                //求和之後就是mid*n,一定是sum,不必再判斷
                ArrayList<Integer> list = new ArrayList<Integer>();
                for(int i = 0; i < n; i++)
                    list.add(first + i);
                res.add(0, list);  //按題目要求順序需要頭部插入
            }
            if(!nIsOdd && sum % n != 0) {  //n爲偶數時不可以除開
                //求和之後結果是mid*n+(last-mid),即mid*n+n/2
                if(mid * n + n / 2 == sum) {
                    ArrayList<Integer> list = new ArrayList<Integer>();
                    for(int i = 0; i < n; i++)
                        list.add(first + i);
                    res.add(0, list);
                }
            }
            //更新各個參數
            n++;
            nIsOdd = !nIsOdd;
            mid = sum / n;
            first = mid + 1 - n / 2;
            if(nIsOdd)  //n爲奇數,first去掉+1
                first--;
        }
        return res;
    }
}

42.和爲S的兩個數字

題目描述

輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。

輸出描述:

對應每個測試案例,輸出兩個數,小的先輸出。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        //乘積最小是最靠近兩側的結果
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(array.length == 0)
            return res;
        int i = 0, j = array.length - 1;  //從兩側向中間遍歷
        while(i < j) {
            if(array[i] + array[j] == sum) {
                res.add(array[i]);
                res.add(array[j]);
                break;
            }
            else if(array[i] + array[j] < sum)
                i++;
            else
                j--;
        }
        return res;
    }
}

43.左旋轉字符串

題目描述

彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!

public class Solution {
    public String LeftRotateString(String str,int n) {
        //先分兩部分,再分別轉置的解法
        if(str == null || n > str.length())
            return "";
        String a = str.substring(0, n);
        String b = str.substring(n);
        return reverse(reverse(a) + reverse(b));
    }
    public String reverse(String str) {
        String res = "";
        for(int i = 0; i < str.length(); i++)
            res = str.charAt(i) + res;
        return res;
    }
}

44.旋轉單詞順序列

題目描述

牛客最近來了一個新員工Fish,每天早晨總是會拿着一本英文雜誌,寫些句子在本子上。同事Cat對Fish寫的內容頗感興趣,有一天他向Fish借來翻看,但卻讀不懂它的意思。例如,“student. a am I”。後來才意識到,這傢伙原來把句子單詞的順序翻轉了,正確的句子應該是“I am a student.”。Cat對一一的翻轉這些單詞順序可不在行,你能幫助他麼?

public class Solution {
    public String ReverseSentence(String str) {
        String[] strArr = str.split(" ");
        if(strArr.length == 0)
            return str;  //原樣返回
        String res = "";
        int i = 0, j = strArr.length - 1;
        while(i < j) {  //reverse
            String temp = strArr[i];
            strArr[i] = strArr[j];
            strArr[j] = temp;
            i++;
            j--;
        }
        for(int k = 0; k < strArr.length - 1; k++)
            res += strArr[k] + " ";
        res += strArr[strArr.length - 1];
        return res;
    }
}

45.撲克牌順子

題目描述

LL今天心情特別好,因爲他去買了一副撲克牌,發現裏面居然有2個大王,2個小王(一副牌原本是54張^_^)...他隨機從中抽出了5張牌,想測測自己的手氣,看看能不能抽到順子,如果抽到的話,他決定去買體育彩票,嘿嘿!!“紅心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是順子.....LL不高興了,他想了想,決定大\小 王可以看成任何數字,並且A看作1,J爲11,Q爲12,K爲13。上面的5張牌就可以變成“1,2,3,4,5”(大小王分別看作2和4),“So Lucky!”。LL決定去買體育彩票啦。 現在,要求你使用這幅牌模擬上面的過程,然後告訴我們LL的運氣如何, 如果牌能組成順子就輸出true,否則就輸出false。爲了方便起見,你可以認爲大小王是0。

public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers.length != 5)
            return false;
        int[] pokers = new int[14];
        int max = 1, min = 13;
        for(int num : numbers) {
            if(num == 0)
                pokers[0]++;
            else{
                if(pokers[num] > 0)  //出現重複
                    return false;
                pokers[num]++;
                if(num < min)
                    min = num;
                if(num > max)
                    max = num;
            }
        }
        if(pokers[0] == 4)  //4個0一定可以
            return true;
        //否則只要沒有重複,且最大最小值差在4以內即可
        else if(max - min > 4)
            return false;
        return true;
    }
}

46.孩子們的遊戲(圓圈中最後剩下的數)

題目描述

每年六一兒童節,牛客都會準備一些小禮物去看望孤兒院的小朋友,今年亦是如此。HF作爲牛客的資深元老,自然也準備了一些小遊戲。其中,有個遊戲是這樣的:首先,讓小朋友們圍成一個大圈。然後,他隨機指定一個數m,讓編號爲0的小朋友開始報數。每次喊到m-1的那個小朋友要出列唱首歌,然後可以在禮品箱中任意的挑選禮物,並且不再回到圈中,從他的下一個小朋友開始,繼續0...m-1報數....這樣下去....直到剩下最後一個小朋友,可以不用表演,並且拿到牛客名貴的“名偵探柯南”典藏版(名額有限哦!!^_^)。請你試着想下,哪個小朋友會得到這份禮品呢?(注:小朋友的編號是從0到n-1)

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        //就是約瑟夫環
        if(n == 0) 
            return -1;
        int s = 0;  //1人環中,就是0
        for(int i = 2; i <= n; i++) {  //2人環到n人環迭代
            s = (s + m) % i;
        }
        return s;
    }
}

47.求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 res = n;
        //利用&&的短路,前面爲假後面不計算,構造遞歸出口
        boolean a = (res > 0) && ((res += Sum_Solution(res - 1)) > 0);
        return res;
    }
}

48.不用加減乘除做加法

題目描述

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

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

49.把字符串轉換成整數

題目描述

將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。

輸入描述:

輸入一個字符串,包括數字字母符號,可以爲空

輸出描述:

如果是合法的數值表達則返回該數字,否則返回0

public class Solution {
    public int StrToInt(String str) {
        if(str == null || str.length() == 0)
            return 0;
        int res = 0;
        int weight = 1;
        if(str.charAt(0) == '-')  //負數
            weight = -1;
        for(int i = str.length() - 1; i >= 0; i--) {  //從右向左遍歷
            char c = str.charAt(i);
            if(c >= '0' && c <= '9') {
                int temp = res + (c - '0') * weight;
                if(temp - res != (c - '0') * weight)  //溢出
                    return 0;
                else
                    res = temp;
            }
            else if((c == '+' || c == '-') && i == 0)  //不是0-9只能是第1位+-號,否則非法
                break;
            else
                return 0;
            weight *= 10;
        }
        return res;
    }
}

50.數組中重複的數字

題目描述

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

public class Solution {
    //    這裏要特別注意~返回任意重複的一個,賦值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        //這個題應該利用下標
        if(length <= 1)
            return false;
        int j = 0;  //下標爲j,初始不能是值和下標相同的,否則跳不出去會產生錯誤結果
        while(j < length && numbers[j] == j)  //找到一個符合要求的初始點
            j++;
        if (j == length)  //全部不符,說明無重複
            return false;
        j = numbers[j];  //這一步至關重要,要知道初始下標j不是跳到的,不能置-1
                         //所以要改爲跳到的第一個下標
        //計算值和下標相同的個數,循環次數要減掉纔行
        int count = 0;
        for(int i = 0; i < length; i++)
            if(numbers[i] == i)
                count++;
        for(int i = 0; i < length - count; i++) {  //此處i只是次數,與下標無關
            if(numbers[j] == -1 || numbers[j] == j) {  //第二個條件重要!跳到陷阱處也說明是重複
                duplication[0] = j;
                return true;
            }
            else {
                int temp = j;
                j = numbers[j];
                numbers[temp] = -1;  //跳到過的置-1
            }
        }
        return false;
    }
}

51.構建乘積數組

題目描述

給定一個數組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]。不能使用除法。

public class Solution {
    public int[] multiply(int[] A) {
        //遍歷一個來回,逐個填充乘進結果數組,用一個變量儲存乘積即可
        int[] res = new int[A.length];
        int left = 1;
        int right = 1;
        for(int i = 0; i < A.length; i++) {
            res[i] = left;
            left *= A[i];
        }
        for(int i = A.length - 1; i >= 0; i--) {
            res[i] *= right;
            right *= A[i];
        }
        return res;
    }
}

52.正則表達式匹配

題目描述

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

public class Solution {
    public boolean match(char[] str, char[] pattern) {
        return match(str, 0, pattern, 0);
    }
    //加上開始下標參數,以便遞歸處理'*'的複雜情況
    public boolean match(char[] str, int i1, char[] pattern, int i2) {
        if(i1 == str.length && i2 == pattern.length)  //都遍歷完,返回true
            return true;
        else if(i2 == pattern.length)  //pattern結束,str還沒結束當然不行
            return false;
        else if(i1 == str.length) {  //str結束,pattern剩下的只能是字符+'*'的組合
            while(i2 + 1 < pattern.length && pattern[i2 + 1] == '*')
                i2 += 2;
            return i2 == pattern.length;
        }
        //兩個都沒結束的情況
        //pattern後面是'*'時
        if(i2 + 1 < pattern.length && pattern[i2 + 1] == '*') {
            if(str[i1] == pattern[i2] || pattern[i2] == '.')  //'*'代表1次或多次,或0次
                 //0次不要丟掉!因爲pattern後面可能有普通字符需要匹配str中字符
                 //如果丟了0次'.'把str中的匹配掉了,pattern後面就沒處匹配了,如"bbbba"和".*a*a"
                return match(str, i1 + 1, pattern, i2 + 2) || 
                       match(str, i1 + 1, pattern, i2) || 
                       match(str, i1, pattern, i2 + 2);
            else  //'*'代表0次
                return match(str, i1, pattern, i2 + 2);
        }
        //pattern後面不是'*'時
        else {
            if(str[i1] == pattern[i2] || pattern[i2] == '.')
                return match(str, i1 + 1, pattern, i2 + 1);
            else
                return false;
        }
    }
}

53.表示數值的字符串

題目描述

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

public class Solution {
    public boolean isNumeric(char[] str) {
        boolean noPoint = true;  //是否沒有'.'
        boolean noE = true;  //是否沒有'E'或'e'
        int pointPos = 0;
        int ePos = 0;
        for(int i = 0; i < str.length; i++) {
            if(str[i] == '.') {
                if(noPoint) {
                    noPoint = false;
                    pointPos = i;
                }
                else  //兩個.
                    return false;
            }
            if(str[i] == 'e' || str[i] == 'E') {
                if(noE) {
                    noE = false;
                    ePos = i;
                }
                else  //兩個e
                    return false;
            }
        }
        if(!noE) {  //有e
            if(pointPos > ePos)
                return false;
            else
                return isIntegerOrFloat(str, ePos + 1, str.length - 1, false) 
                        && isIntegerOrFloat(str, 0, ePos - 1, true);
        }
        else
            return isIntegerOrFloat(str, 0, str.length - 1, true);
    }

    public boolean isNum(char c) {
        return c >= '0' && c <= '9';
    }

    public boolean isIntegerOrFloat(char[] str, int start, int end, boolean canBeFloat) {
        if(start > end)
            return false;
        if(start == end)
            return isNum(str[start]);
        if(str[start] == '+' || str[start] == '-')
            start++;
        while(start <= end) {
            // 不用擔心‘.’超過一個因爲主函數判斷過了
            if (isNum(str[start]) || canBeFloat && str[start] == '.') { 
                start++;
            } else {
                return false;
            }
        }
        return start == end + 1;
    }
}

54.字符流中第一個不重複的字符

題目描述

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

輸出描述:

如果當前字符流沒有存在出現一次的字符,返回#字符。

public class Solution {
    String s = "";
    int[] charNums = new int[256];
    //Insert one char from stringstream
    public void Insert(char ch) {
        s += ch;
        charNums[ch]++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce() {
        //從字符串s開始遍歷
        for(int i = 0; i < s.length(); i++)
            if(charNums[s.charAt(i)] == 1)
                return s.charAt(i);
        return '#';
    }
}

55.鏈表中環的入口節點

題目描述

給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead == null)
            return null;
        else if(pHead.next == null)
            return null;
        ListNode fast = pHead, slow = pHead, start = pHead;
        while(fast != null) {
            if(fast.next != null)
                fast = fast.next.next;
            else
                fast = fast.next;  //此時fast已經爲null
            slow = slow.next;
            if(fast == slow) {  //相等處fast多走了整數個環長,也是slow走的長度
                while(start != slow) {  //再走1個環外長度就剛好會遇到
                    start = start.next;
                    slow = slow.next;
                }
                break;
            }
        }
        if(fast == null)  //無環
            return null;
        else
            return start;
    }
}

56.刪除鏈表中重複的節點

題目描述

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

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        if(pHead == null)
            return null;
        ListNode res = new ListNode(0);  //pHead是可能被刪掉的,所以要保存一個頭節點
        res.next = pHead;
        ListNode pre = res;  //前置節點,刪除時會用到
        ListNode l = pHead;
        int cur = pHead.val;
        while(l != null) {
            int count = 0;
            while(l != null && l.val == cur) {  //統計相同元素個數
                l = l.next;
                count++;
            }
            if(count > 1)
                pre.next = l;  //越過重複值的節點,注意pre不動
            else
                pre = pre.next;  //後移
            if(l != null)
                cur = l.val;  //更新cur
        }
        return res.next;
    }
}

57.二叉樹的下一個節點

題目描述

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

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

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        if(pNode == null)
            return null;
        if(pNode.right != null)
            return inOrderFirstNode(pNode.right);
        //pNode右爲空,則當前子樹已經遍歷完
        TreeLinkNode father = pNode.next;
        //要用循環纔行
        while(father != null) {
            if(father.left == pNode)
                return father;
            //否則就是father爲根的子樹也遍歷完
            else {
                pNode = father;
                father = father.next;
            }
        }
        return null;
    }
    //求中序遍歷的第一個節點
    public TreeLinkNode inOrderFirstNode(TreeLinkNode root) {
        //調用時讓這個函數輸入不爲空,不包含返回空的情況,便於理解
        if(root.left == null)
            return root;
        else
            return inOrderFirstNode(root.left);
    }
}

58.對稱的二叉樹

題目描述

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

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        if(pRoot == null)
            return true;
        else  //左右子樹互爲鏡像即對稱
            return isMirror(pRoot.left, pRoot.right);
    }
    //判斷是否是鏡像
    boolean isMirror(TreeNode root1, TreeNode root2) {
        if(root1 == null && root2 == null)
            return true;
        else if(root1 == null || root2 == null)
            return false;
        if(root1.val != root2.val)
            return false;
        else
            return isMirror(root1.left, root2.right) && isMirror(root1.right, root2.left);
    }
}

59.按之字形打印二叉樹

題目描述

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        //還是層次遍歷
        //和逐行打印相同,只是新增加一個變量標記,偶數行要逆序
        ArrayList<ArrayList<Integer> > res = new ArrayList<>();
        if(pRoot == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(pRoot);
        TreeNode mark = new TreeNode(0);
        queue.offer(mark);
        ArrayList<Integer> list = new ArrayList<>();
        boolean reverse = false;  //逆序標記,偶數行爲true
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur == mark) {  //檢測到標記
                if (reverse) {
                    Collections.reverse(list);
                }
                res.add(list);
                list = new ArrayList<>();
                reverse = !reverse;
                if (!queue.isEmpty()) {  //隊列還有元素時,標記塞回隊列
                    queue.offer(cur);
                }
            } else {
                list.add(cur.val);
                if (cur.left != null)
                    queue.offer(cur.left);
                if (cur.right != null)
                    queue.offer(cur.right);
            }
        }
        return res;
    }
}

60.把二叉樹打印成多行

題目描述

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

import java.util.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        //同上題,去掉reverse即可
        //層次遍歷,標記每行結束
        ArrayList<ArrayList<Integer> > res = new ArrayList<>();
        if(pRoot == null)
            return res;
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(pRoot);
        TreeNode mark = new TreeNode(0);
        queue.offer(mark);
        ArrayList<Integer> list = new ArrayList<>();
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            if (cur == mark) {  //檢測到標記
                res.add(list);
                list = new ArrayList<>();
                if (!queue.isEmpty()) {  //隊列還有元素時,標記塞回隊列
                    queue.offer(cur);
                }
            } else {
                list.add(cur.val);
                if (cur.left != null)
                    queue.offer(cur.left);
                if (cur.right != null)
                    queue.offer(cur.right);
            }
        }
        return res;
    }
}

61.序列化二叉樹

題目描述

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

public class Solution {
    String Serialize(TreeNode root) {
        //前序遍歷序列化,","分隔節點,"#"表示null
        StringBuilder res = new StringBuilder();
        if(root == null) {
            res.append("#,");
            return res.toString();
        }
        res.append(root.val);
        res.append(",");
        res.append(Serialize(root.left));
        res.append(Serialize(root.right));
        return res.toString();
    }
    TreeNode Deserialize(String str) {
        if(str == null)
            return null;
        String[] strArr = str.split(",");  //獲取節點String數組
        int[] index = new int[]{-1};
        return DeserializeStrArr(strArr, index);
    }
    TreeNode DeserializeStrArr(String[] strArr, int[] index) {
        //一定要最先修改index,因爲遞歸1次就檢查了1個節點,index要後移
        //這樣可以保證每次遞歸的index都是不同的
        index[0]++;
        TreeNode root = null;
        if(!strArr[index[0]].equals("#")) {  //非null節點
            root = new TreeNode(Integer.valueOf(strArr[index[0]]));
            root.left = DeserializeStrArr(strArr, index);
            root.right = DeserializeStrArr(strArr, index);
        }
        return root;
    }
}

62.二叉搜索樹的第k個節點

題目描述

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8)    中,按結點數值大小順序第三小結點的值爲4。

import java.util.ArrayList;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k) {
        //中序遍歷,數到k即可
        if(k <= 0)
            return null;
        int[] count = new int[1];
        count[0] = k;
        return inOrder(pRoot, count);
    }
    TreeNode inOrder(TreeNode pRoot, int[] count) {
        if (pRoot != null) {
            TreeNode leftRes = inOrder(pRoot.left, count);
            if (leftRes != null) {  //左子樹中找到第k小
                return leftRes;
            }
            if (count[0] == 1) {  //當前節點爲第k小
                return pRoot;
            }
            count[0]--;
            return inOrder(pRoot.right, count);
        }
        return null;
    }
}

63.數據流的中位數

題目描述

如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從數據流中讀出偶數個數值,那麼中位數就是所有數值排序之後中間兩個數的平均值。我們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。

import java.util.ArrayList;
public class Solution {
    ArrayList<Integer> list = new ArrayList<>();  // 有序列表

    public void Insert(Integer num) {
        if(list.size() == 0) {
            list.add(num);
            return;
        }
        if(num >= list.get(list.size() - 1))  // 尾插單獨判斷下
            list.add(num);
        else {
            list.add(binarySearchIndex(num), num);
        }
    }

    // 二分查找插入位置
    public int binarySearchIndex(int num) {
        int start = 0, end = list.size() - 1;
        while (start < end) {
            int mid = (start + end) / 2;  
            int midVal = list.get(mid);
            if (midVal == num || 
                    midVal >= num && mid >= 1 && list.get(mid - 1) < num)
                return mid;
            else if (midVal < num)
                start = mid + 1;  // 較小值不會是插入位置,因此跳過
            else
                end = mid;  // 較大值有可能是插入位置
        }
        return start;
    }

    public Double GetMedian() {
        int size = list.size();
        if(size == 0)
            return null;
        else if(size % 2 == 1)
            return list.get((size - 1) / 2) * 1.0;  //注意下標和size的關係,別忘了-1
        else
            return (list.get((size - 1) / 2)
                    + list.get((size - 1) / 2 + 1)) / 2.0;
    }
}

64.滑動窗口的最大值

題目描述

給定一個數組和滑動窗口的大小,找出所有滑動窗口裏數值的最大值。例如,如果輸入數組{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.ArrayList;
import java.util.Queue;
import java.util.LinkedList;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if(size <= 0 || size > num.length)
            return res;
        Queue<Integer> queue = new LinkedList<Integer>();  //保持隊列大小爲size,更新max
        int maxIndex = 0, max = Integer.MIN_VALUE;
        for(int i = 0; i < num.length; i++) {
            if(queue.size() < size) {  //初始隊列沒滿的時候
                queue.offer(num[i]);
                if(num[i] >= max) {  //要加上等於,以便maxIndex更新到右邊第一個max位置
                    max = num[i];
                    maxIndex = i;
                }
            }
            else {
                res.add(max);  //先儲存max
                //看出隊的是不是當前max,用下標判斷
                if(maxIndex == i - size) {  //出隊的是當前max
                    max = num[i - size + 1];
                    maxIndex = i - size + 1;
                    for(int j = i - size + 2; j <= i; j++)  //循環取得新max
                        if(num[j] >= max) {
                            max = num[j];
                            maxIndex = j;
                        }
                }
                //如果不是,就不用管出隊的值,只有新值num[i]>=max時更新max即可
                else if(num[i] >= max) {  //當前爲最大值,更新max和maxIndex
                    max = num[i];
                    maxIndex = i;
                }
                queue.poll();  //無論前面如何都要出隊和入隊
                queue.offer(num[i]);
            }
        }
        res.add(max);
        return res;
    }
}

65.矩陣中的路徑

題目描述

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能再次進入這個格子。 例如 a b c e s f c s a d e e 這樣的3 X 4 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子。

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
        for(int i = 0; i < matrix.length; i++) {  //不同的起始點
            boolean[] flag = new boolean[matrix.length];  //每個起始點新建標記數組
            if(backtracking(i, matrix, rows, cols, str, 0, flag))
                return true;
        }
        return false;
    }
    //回溯算法
    public boolean backtracking(int start, char[] matrix, int rows, int cols, 
                                char[] str, int strIndex, boolean[] flag) {
        if(strIndex == str.length)  //字符串比較完成
            return true;
        if(start < 0 || start >= matrix.length || flag[start] || matrix[start] != str[strIndex])
            return false;
        else {  //求上下左右的座標
            flag[start] = true;
            int left = start % cols == 0 ? -1 : start - 1;  //是否是每行開始
            int right = (start + 1) % cols == 0 ? -1 : start + 1;  //是否是每行結束
            int up = start - cols;
            int down = start + cols;
            return backtracking(left, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(right, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(up, matrix, rows, cols, str, strIndex + 1, flag)
                || backtracking(down, matrix, rows, cols, str, strIndex + 1, flag);
        }
    }
}

66.機器人的運動範圍

題目描述

地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k爲18時,機器人能夠進入方格(35,37),因爲3+5+3+7 = 18。但是,它不能進入方格(35,38),因爲3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        boolean[][] flag = new boolean[rows][cols];  //標記是否走過
        return backtracking(0, 0, threshold, rows, cols, flag);
    }
    //回溯算法
    public int backtracking(int i, int j, int threshold, int rows, int cols, boolean[][] flag) {
        if(i < 0 || i >= rows || j < 0 || j >= cols || digitAdd(i, j) > threshold || flag[i][j])
            return 0;
        else {
            flag[i][j] = true;
            return 1 + backtracking(i - 1, j, threshold, rows, cols, flag)
                + backtracking(i + 1, j, threshold, rows, cols, flag)
                + backtracking(i, j - 1, threshold, rows, cols, flag)
                + backtracking(i, j + 1, threshold, rows, cols, flag);
        }
    }
    //數位求和
    public int digitAdd(int a, int b) {
        int sum = 0;
        while(a > 0) {
            sum += a % 10;
            a /= 10;
        }
        while(b > 0) {
            sum += b % 10;
            b /= 10;
        }
        return sum;
    }
}

END

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