劍指 Offer 全解(Java 版)

本文轉自我的博客:CyC2018/CS-Noteshtml

CyC2018/CS-Notes - GitHub

3. 數組中重複的數字

NowCoderjava

題目描述

在一個長度爲 n 的數組裏的全部數字都在 0 到 n-1 的範圍內。數組中某些數字是重複的,但不知道有幾個數字是重複的,也不知道每一個數字重複幾回。請找出數組中任意一個重複的數字。node

Input:
{2, 3, 1, 0, 2, 5}

Output:
2
複製代碼

解題思路

要求時間複雜度 O(N),空間複雜度 O(1)。所以不能使用排序的方法,也不能使用額外的標記數組。c++

對於這種數組元素在 [0, n-1] 範圍內的問題,能夠將值爲 i 的元素調整到第 i 個位置上進行求解。git

以 (2, 3, 1, 0, 2, 5) 爲例,遍歷到位置 4 時,該位置上的數爲 2,可是第 2 個位置上已經有一個 2 的值了,所以能夠知道 2 重複:github


public boolean duplicate(int[] nums, int length, int[] duplication) {
    if (nums == null || length <= 0)
        return false;
    for (int i = 0; i < length; i++) {
        while (nums[i] != i) {
            if (nums[i] == nums[nums[i]]) {
                duplication[0] = nums[i];
                return true;
            }
            swap(nums, i, nums[i]);
        }
    }
    return false;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
複製代碼

4. 二維數組中的查找

NowCoder面試

題目描述

給定一個二維數組,其每一行從左到右遞增排序,從上到下也是遞增排序。給定一個數,判斷這個數是否在該二維數組中。正則表達式

Consider the following matrix:
[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

Given target = 5, return true.
Given target = 20, return false.
複製代碼

解題思路

要求時間複雜度 O(M + N),空間複雜度 O(1)。其中 M 爲行數,N 爲 列數。算法

該二維數組中的一個數,小於它的數必定在其左邊,大於它的數必定在其下邊。所以,從右上角開始查找,就能夠根據 target 和當前元素的大小關係來縮小查找區間,當前元素的查找區間爲左下角的全部元素。數組


public boolean Find(int target, int[][] matrix) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
        return false;
    int rows = matrix.length, cols = matrix[0].length;
    int r = 0, c = cols - 1; // 從右上角開始
    while (r <= rows - 1 && c >= 0) {
        if (target == matrix[r][c])
            return true;
        else if (target > matrix[r][c])
            r++;
        else
            c--;
    }
    return false;
}
複製代碼

5. 替換空格

NowCoder

題目描述

將一個字符串中的空格替換成 "%20"。

Input:
"A B"

Output:
"A%20B"
複製代碼

解題思路

在字符串尾部填充任意字符,使得字符串的長度等於替換以後的長度。由於一個空格要替換成三個字符(%20),所以當遍歷到一個空格時,須要在尾部填充兩個任意字符。

令 P1 指向字符串原來的末尾位置,P2 指向字符串如今的末尾位置。P1 和 P2 從後向前遍歷,當 P1 遍歷到一個空格時,就須要令 P2 指向的位置依次填充 02%(注意是逆序的),不然就填充上 P1 指向字符的值。

從後向前遍是爲了在改變 P2 所指向的內容時,不會影響到 P1 遍歷原來字符串的內容。


public String replaceSpace(StringBuffer str) {
    int P1 = str.length() - 1;
    for (int i = 0; i <= P1; i++)
        if (str.charAt(i) == ' ')
            str.append(" ");

    int P2 = str.length() - 1;
    while (P1 >= 0 && P2 > P1) {
        char c = str.charAt(P1--);
        if (c == ' ') {
            str.setCharAt(P2--, '0');
            str.setCharAt(P2--, '2');
            str.setCharAt(P2--, '%');
        } else {
            str.setCharAt(P2--, c);
        }
    }
    return str.toString();
}
複製代碼

6. 從尾到頭打印鏈表

NowCoder

題目描述

從尾到頭反過來打印出每一個結點的值。


解題思路

使用遞歸

要逆序打印鏈表 1->2->3(3,2,1),能夠先逆序打印鏈表 2->3(3,2),最後再打印第一個節點 1。而鏈表 2->3 能夠當作一個新的鏈表,要逆序打印該鏈表能夠繼續使用求解函數,也就是在求解函數中調用本身,這就是遞歸函數。

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (listNode != null) {
        ret.addAll(printListFromTailToHead(listNode.next));
        ret.add(listNode.val);
    }
    return ret;
}
複製代碼

使用頭插法

使用頭插法能夠獲得一個逆序的鏈表。

頭結點和第一個節點的區別:

  • 頭結點是在頭插法中使用的一個額外節點,這個節點不存儲值;
  • 第一個節點就是鏈表的第一個真正存儲值的節點。

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    // 頭插法構建逆序鏈表
    ListNode head = new ListNode(-1);
    while (listNode != null) {
        ListNode memo = listNode.next;
        listNode.next = head.next;
        head.next = listNode;
        listNode = memo;
    }
    // 構建 ArrayList
    ArrayList<Integer> ret = new ArrayList<>();
    head = head.next;
    while (head != null) {
        ret.add(head.val);
        head = head.next;
    }
    return ret;
}
複製代碼

使用棧

棧具備後進先出的特色,在遍歷鏈表時將值按順序放入棧中,最後出棧的順序即爲逆序。


public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
    Stack<Integer> stack = new Stack<>();
    while (listNode != null) {
        stack.add(listNode.val);
        listNode = listNode.next;
    }
    ArrayList<Integer> ret = new ArrayList<>();
    while (!stack.isEmpty())
        ret.add(stack.pop());
    return ret;
}
複製代碼

7. 重建二叉樹

NowCoder

題目描述

根據二叉樹的前序遍歷和中序遍歷的結果,重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。


解題思路

前序遍歷的第一個值爲根節點的值,使用這個值將中序遍歷結果分紅兩部分,左部分爲樹的左子樹中序遍歷結果,右部分爲樹的右子樹中序遍歷的結果。


// 緩存中序遍歷數組每一個值對應的索引
private Map<Integer, Integer> indexForInOrders = new HashMap<>();

public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
    for (int i = 0; i < in.length; i++)
        indexForInOrders.put(in[i], i);
    return reConstructBinaryTree(pre, 0, pre.length - 1, 0);
}

private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
    if (preL > preR)
        return null;
    TreeNode root = new TreeNode(pre[preL]);
    int inIndex = indexForInOrders.get(root.val);
    int leftTreeSize = inIndex - inL;
    root.left = reConstructBinaryTree(pre, preL + 1, preL + leftTreeSize, inL);
    root.right = reConstructBinaryTree(pre, preL + leftTreeSize + 1, preR, inL + leftTreeSize + 1);
    return root;
}
複製代碼

8. 二叉樹的下一個結點

NowCoder

題目描述

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

public class TreeLinkNode {

    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
複製代碼

解題思路

① 若是一個節點的右子樹不爲空,那麼該節點的下一個節點是右子樹的最左節點;


② 不然,向上找第一個左連接指向的樹包含該節點的祖先節點。


public TreeLinkNode GetNext(TreeLinkNode pNode) {
    if (pNode.right != null) {
        TreeLinkNode node = pNode.right;
        while (node.left != null)
            node = node.left;
        return node;
    } else {
        while (pNode.next != null) {
            TreeLinkNode parent = pNode.next;
            if (parent.left == pNode)
                return parent;
            pNode = pNode.next;
        }
    }
    return null;
}
複製代碼

9. 用兩個棧實現隊列

NowCoder

題目描述

用兩個棧來實現一個隊列,完成隊列的 Push 和 Pop 操做。

解題思路

in 棧用來處理入棧(push)操做,out 棧用來處理出棧(pop)操做。一個元素進入 in 棧以後,出棧的順序被反轉。當元素要出棧時,須要先進入 out 棧,此時元素出棧順序再一次被反轉,所以出棧順序就和最開始入棧順序是相同的,先進入的元素先退出,這就是隊列的順序。


Stack<Integer> in = new Stack<Integer>();
Stack<Integer> out = new Stack<Integer>();

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

public int pop() throws Exception {
    if (out.isEmpty())
        while (!in.isEmpty())
            out.push(in.pop());

    if (out.isEmpty())
        throw new Exception("queue is empty");

    return out.pop();
}
複製代碼

10.1 斐波那契數列

NowCoder

題目描述

求斐波那契數列的第 n 項,n <= 39。

f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right.

解題思路

若是使用遞歸求解,會重複計算一些子問題。例如,計算 f(4) 須要計算 f(3) 和 f(2),計算 f(3) 須要計算 f(2) 和 f(1),能夠看到 f(2) 被重複計算了。

遞歸是將一個問題劃分紅多個子問題求解,動態規劃也是如此,可是動態規劃會把子問題的解緩存起來,從而避免重複求解子問題。

public int Fibonacci(int n) {
    if (n <= 1)
        return n;
    int[] fib = new int[n + 1];
    fib[1] = 1;
    for (int i = 2; i <= n; i++)
        fib[i] = fib[i - 1] + fib[i - 2];
    return fib[n];
}
複製代碼

考慮到第 i 項只與第 i-1 和第 i-2 項有關,所以只須要存儲前兩項的值就能求解第 i 項,從而將空間複雜度由 O(N) 下降爲 O(1)。

public int Fibonacci(int n) {
    if (n <= 1)
        return n;
    int pre2 = 0, pre1 = 1;
    int fib = 0;
    for (int i = 2; i <= n; i++) {
        fib = pre2 + pre1;
        pre2 = pre1;
        pre1 = fib;
    }
    return fib;
}
複製代碼

因爲待求解的 n 小於 40,所以能夠將前 40 項的結果先進行計算,以後就能以 O(1) 時間複雜度獲得第 n 項的值。

public class Solution {

    private int[] fib = new int[40];

    public Solution() {
        fib[1] = 1;
        for (int i = 2; i < fib.length; i++)
            fib[i] = fib[i - 1] + fib[i - 2];
    }

    public int Fibonacci(int n) {
        return fib[n];
    }
}
複製代碼

10.2 矩形覆蓋

NowCoder

題目描述

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

解題思路

當 n 爲 1 時,只有一種覆蓋方法:

當 n 爲 2 時,有兩種覆蓋方法:

要覆蓋 2*n 的大矩形,能夠先覆蓋 2*1 的矩形,再覆蓋 2*(n-1) 的矩形;或者先覆蓋 2*2 的矩形,再覆蓋 2*(n-2) 的矩形。而覆蓋 2*(n-1) 和 2*(n-2) 的矩形能夠當作子問題。該問題的遞推公式以下:

f(n)=\left\{\begin{array}{rcl}1&&{n=1}\\2&&{n=2}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right.
public int RectCover(int n) {
    if (n <= 2)
        return n;
    int pre2 = 1, pre1 = 2;
    int result = 0;
    for (int i = 3; i <= n; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    }
    return result;
}
複製代碼

10.3 跳臺階

NowCoder

題目描述

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

解題思路

當 n = 1 時,只有一種跳法:

當 n = 2 時,有兩種跳法:

跳 n 階臺階,能夠先跳 1 階臺階,再跳 n-1 階臺階;或者先跳 2 階臺階,再跳 n-2 階臺階。而 n-1 和 n-2 階臺階的跳法能夠當作子問題,該問題的遞推公式爲:

f(n)=\left\{\begin{array}{rcl}1&&{n=1}\\2&&{n=2}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right.
public int JumpFloor(int n) {
    if (n <= 2)
        return n;
    int pre2 = 1, pre1 = 2;
    int result = 1;
    for (int i = 2; i < n; i++) {
        result = pre2 + pre1;
        pre2 = pre1;
        pre1 = result;
    }
    return result;
}
複製代碼

10.4 變態跳臺階

NowCoder

題目描述

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

解題思路

動態規劃

public int JumpFloorII(int target) {
    int[] dp = new int[target];
    Arrays.fill(dp, 1);
    for (int i = 1; i < target; i++)
        for (int j = 0; j < i; j++)
            dp[i] += dp[j];
    return dp[target - 1];
}
複製代碼

數學推導

跳上 n-1 級臺階,能夠從 n-2 級跳 1 級上去,也能夠從 n-3 級跳 2 級上去...,那麼

f(n-1) = f(n-2) + f(n-3) + ... + f(0)
複製代碼

一樣,跳上 n 級臺階,能夠從 n-1 級跳 1 級上去,也能夠從 n-2 級跳 2 級上去... ,那麼

f(n) = f(n-1) + f(n-2) + ... + f(0)
複製代碼

綜上可得

f(n) - f(n-1) = f(n-1)
複製代碼

f(n) = 2*f(n-1)
複製代碼

因此 f(n) 是一個等比數列

public int JumpFloorII(int target) {
    return (int) Math.pow(2, target - 1);
}
複製代碼

11. 旋轉數組的最小數字

NowCoder

題目描述

把一個數組最開始的若干個元素搬到數組的末尾,咱們稱之爲數組的旋轉。輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。

解題思路

將旋轉數組對半分能夠獲得一個包含最小元素的新旋轉數組,以及一個非遞減排序的數組。新的旋轉數組的數組元素是原數組的一半,從而將問題規模減小了一半,這種折半性質的算法的時間複雜度爲 O(logN)(爲了方便,這裏將 log2N 寫爲 logN)。

此時問題的關鍵在於肯定對半分獲得的兩個數組哪個是旋轉數組,哪個是非遞減數組。咱們很容易知道非遞減數組的第一個元素必定小於等於最後一個元素。

經過修改二分查找算法進行求解(l 表明 low,m 表明 mid,h 表明 high):

  • 當 nums[m] <= nums[h] 時,表示 [m, h] 區間內的數組是非遞減數組,[l, m] 區間內的數組是旋轉數組,此時令 h = m;
  • 不然 [m + 1, h] 區間內的數組是旋轉數組,令 l = m + 1。
public int minNumberInRotateArray(int[] nums) {
    if (nums.length == 0)
        return 0;
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] <= nums[h])
            h = m;
        else
            l = m + 1;
    }
    return nums[l];
}
複製代碼

若是數組元素容許重複,會出現一個特殊的狀況:nums[l] == nums[m] == nums[h],此時沒法肯定解在哪一個區間,須要切換到順序查找。例如對於數組 {1,1,1,0,1},l、m 和 h 指向的數都爲 1,此時沒法知道最小數字 0 在哪一個區間。

public int minNumberInRotateArray(int[] nums) {
    if (nums.length == 0)
        return 0;
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[l] == nums[m] && nums[m] == nums[h])
            return minNumber(nums, l, h);
        else if (nums[m] <= nums[h])
            h = m;
        else
            l = m + 1;
    }
    return nums[l];
}

private int minNumber(int[] nums, int l, int h) {
    for (int i = l; i < h; i++)
        if (nums[i] > nums[i + 1])
            return nums[i + 1];
    return nums[l];
}
複製代碼

12. 矩陣中的路徑

NowCoder

題目描述

判斷在一個矩陣中是否存在一條包含某字符串全部字符的路徑。路徑能夠從矩陣中的任意一個格子開始,每一步能夠在矩陣中向上下左右移動一個格子。若是一條路徑通過了矩陣中的某一個格子,則該路徑不能再進入該格子。

例以下面的矩陣包含了一條 bfce 路徑。

解題思路

使用回溯法(backtracking)進行求解,它是一種暴力搜索方法,經過搜索全部可能的結果來求解問題。回溯法在一次搜索結束時須要進行回溯(回退),將這一次搜索過程當中設置的狀態進行清除,從而開始一次新的搜索過程。例以下圖示例中,從 f 開始,下一步有 4 種搜索可能,若是先搜索 b,須要將 b 標記爲已經使用,防止重複使用。在這一次搜索結束以後,須要將 b 的已經使用狀態清除,並搜索 c。

本題的輸入是數組而不是矩陣(二維數組),所以須要先將數組轉換成矩陣。

private final static int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
private int rows;
private int cols;

public boolean hasPath(char[] array, int rows, int cols, char[] str) {
    if (rows == 0 || cols == 0) return false;
    this.rows = rows;
    this.cols = cols;
    boolean[][] marked = new boolean[rows][cols];
    char[][] matrix = buildMatrix(array);
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++)
            if (backtracking(matrix, str, marked, 0, i, j))
                return true;

    return false;
}

private boolean backtracking(char[][] matrix, char[] str, boolean[][] marked, int pathLen, int r, int c) {

    if (pathLen == str.length) return true;
    if (r < 0 || r >= rows || c < 0 || c >= cols
            || matrix[r][c] != str[pathLen] || marked[r][c]) {

        return false;
    }
    marked[r][c] = true;
    for (int[] n : next)
        if (backtracking(matrix, str, marked, pathLen + 1, r + n[0], c + n[1]))
            return true;
    marked[r][c] = false;
    return false;
}

private char[][] buildMatrix(char[] array) {
    char[][] matrix = new char[rows][cols];
    for (int r = 0, idx = 0; r < rows; r++)
        for (int c = 0; c < cols; c++)
            matrix[r][c] = array[idx++];
    return matrix;
}
複製代碼

13. 機器人的運動範圍

NowCoder

題目描述

地上有一個 m 行和 n 列的方格。一個機器人從座標 (0, 0) 的格子開始移動,每一次只能向左右上下四個方向移動一格,可是不能進入行座標和列座標的數位之和大於 k 的格子。

例如,當 k 爲 18 時,機器人可以進入方格 (35,37),由於 3+5+3+7=18。可是,它不能進入方格 (35,38),由於 3+5+3+8=19。請問該機器人可以達到多少個格子?

解題思路

使用深度優先搜索(Depth First Search,DFS)方法進行求解。回溯是深度優先搜索的一種特例,它在一次搜索過程當中須要設置一些本次搜索過程的局部狀態,並在本次搜索結束以後清除狀態。而普通的深度優先搜索並不須要使用這些局部狀態,雖然仍是有可能設置一些全局狀態。

private static final int[][] next = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};
private int cnt = 0;
private int rows;
private int cols;
private int threshold;
private int[][] digitSum;

public int movingCount(int threshold, int rows, int cols) {
    this.rows = rows;
    this.cols = cols;
    this.threshold = threshold;
    initDigitSum();
    boolean[][] marked = new boolean[rows][cols];
    dfs(marked, 0, 0);
    return cnt;
}

private void dfs(boolean[][] marked, int r, int c) {
    if (r < 0 || r >= rows || c < 0 || c >= cols || marked[r][c])
        return;
    marked[r][c] = true;
    if (this.digitSum[r][c] > this.threshold)
        return;
    cnt++;
    for (int[] n : next)
        dfs(marked, r + n[0], c + n[1]);
}

private void initDigitSum() {
    int[] digitSumOne = new int[Math.max(rows, cols)];
    for (int i = 0; i < digitSumOne.length; i++) {
        int n = i;
        while (n > 0) {
            digitSumOne[i] += n % 10;
            n /= 10;
        }
    }
    this.digitSum = new int[rows][cols];
    for (int i = 0; i < this.rows; i++)
        for (int j = 0; j < this.cols; j++)
            this.digitSum[i][j] = digitSumOne[i] + digitSumOne[j];
}
複製代碼

14. 剪繩子

Leetcode

題目描述

把一根繩子剪成多段,而且使得每段的長度乘積最大。

n = 2
return 1 (2 = 1 + 1)

n = 10
return 36 (10 = 3 + 3 + 4)
複製代碼

解題思路

貪心

儘量多剪長度爲 3 的繩子,而且不容許有長度爲 1 的繩子出現。若是出現了,就從已經切好長度爲 3 的繩子中拿出一段與長度爲 1 的繩子從新組合,把它們切成兩段長度爲 2 的繩子。

證實:當 n >= 5 時,3(n - 3) - n = 2n - 9 > 0,且 2(n - 2) - n = n - 4 > 0。所以在 n >= 5 的狀況下,將繩子剪成一段爲 2 或者 3,獲得的乘積會更大。又由於 3(n - 3) - 2(n - 2) = n - 5 >= 0,因此剪成一段長度爲 3 比長度爲 2 獲得的乘積更大。

public int integerBreak(int n) {
    if (n < 2)
        return 0;
    if (n == 2)
        return 1;
    if (n == 3)
        return 2;
    int timesOf3 = n / 3;
    if (n - timesOf3 * 3 == 1)
        timesOf3--;
    int timesOf2 = (n - timesOf3 * 3) / 2;
    return (int) (Math.pow(3, timesOf3)) * (int) (Math.pow(2, timesOf2));
}
複製代碼

動態規劃

public int integerBreak(int n) {
    int[] dp = new int[n + 1];
    dp[1] = 1;
    for (int i = 2; i <= n; i++)
        for (int j = 1; j < i; j++)
            dp[i] = Math.max(dp[i], Math.max(j * (i - j), dp[j] * (i - j)));
    return dp[n];
}
複製代碼

15. 二進制中 1 的個數

NowCoder

題目描述

輸入一個整數,輸出該數二進制表示中 1 的個數。

n&(n-1)

該位運算去除 n 的位級表示中最低的那一位。

n       : 10110100
n-1     : 10110011
n&(n-1) : 10110000
複製代碼

時間複雜度:O(M),其中 M 表示 1 的個數。

public int NumberOf1(int n) {
    int cnt = 0;
    while (n != 0) {
        cnt++;
        n &= (n - 1);
    }
    return cnt;
}
複製代碼

Integer.bitCount()

public int NumberOf1(int n) {
    return Integer.bitCount(n);
}
複製代碼

16. 數值的整數次方

NowCoder

題目描述

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

解題思路

下面的討論中 x 表明 base,n 表明 exponent。

x^n=\left\{\begin{array}{rcl}(x*x)^{n/2}&&{n\%2=0}\\x*(x*x)^{n/2}&&{n\%2=1}\end{array}\right.

由於 (x*x)n/2 能夠經過遞歸求解,而且每次遞歸 n 都減少一半,所以整個算法的時間複雜度爲 O(logN)。

public double Power(double base, int exponent) {
    if (exponent == 0)
        return 1;
    if (exponent == 1)
        return base;
    boolean isNegative = false;
    if (exponent < 0) {
        exponent = -exponent;
        isNegative = true;
    }
    double pow = Power(base * base, exponent / 2);
    if (exponent % 2 != 0)
        pow = pow * base;
    return isNegative ? 1 / pow : pow;
}
複製代碼

17. 打印從 1 到最大的 n 位數

題目描述

輸入數字 n,按順序打印出從 1 到最大的 n 位十進制數。好比輸入 3,則打印出 一、二、3 一直到最大的 3 位數即 999。

解題思路

因爲 n 可能會很是大,所以不能直接用 int 表示數字,而是用 char 數組進行存儲。

使用回溯法獲得全部的數。

public void print1ToMaxOfNDigits(int n) {
    if (n <= 0)
        return;
    char[] number = new char[n];
    print1ToMaxOfNDigits(number, 0);
}

private void print1ToMaxOfNDigits(char[] number, int digit) {
    if (digit == number.length) {
        printNumber(number);
        return;
    }
    for (int i = 0; i < 10; i++) {
        number[digit] = (char) (i + '0');
        print1ToMaxOfNDigits(number, digit + 1);
    }
}

private void printNumber(char[] number) {
    int index = 0;
    while (index < number.length && number[index] == '0')
        index++;
    while (index < number.length)
        System.out.print(number[index++]);
    System.out.println();
}
複製代碼

18.1 在 O(1) 時間內刪除鏈表節點

解題思路

① 若是該節點不是尾節點,那麼能夠直接將下一個節點的值賦給該節點,而後令該節點指向下下個節點,再刪除下一個節點,時間複雜度爲 O(1)。

② 不然,就須要先遍歷鏈表,找到節點的前一個節點,而後讓前一個節點指向 null,時間複雜度爲 O(N)。

綜上,若是進行 N 次操做,那麼大約須要操做節點的次數爲 N-1+N=2N-1,其中 N-1 表示 N-1 個不是尾節點的每一個節點以 O(1) 的時間複雜度操做節點的總次數,N 表示 1 個尾節點以 O(N) 的時間複雜度操做節點的總次數。(2N-1)/N ~ 2,所以該算法的平均時間複雜度爲 O(1)。

public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
    if (head == null || tobeDelete == null)
        return null;
    if (tobeDelete.next != null) {
        // 要刪除的節點不是尾節點
        ListNode next = tobeDelete.next;
        tobeDelete.val = next.val;
        tobeDelete.next = next.next;
    } else {
        if (head == tobeDelete)
             // 只有一個節點
            head = null;
        else {
            ListNode cur = head;
            while (cur.next != tobeDelete)
                cur = cur.next;
            cur.next = null;
        }
    }
    return head;
}
複製代碼

18.2 刪除鏈表中重複的結點

NowCoder

題目描述

解題描述

public ListNode deleteDuplication(ListNode pHead) {
    if (pHead == null || pHead.next == null)
        return pHead;
    ListNode next = pHead.next;
    if (pHead.val == next.val) {
        while (next != null && pHead.val == next.val)
            next = next.next;
        return deleteDuplication(next);
    } else {
        pHead.next = deleteDuplication(pHead.next);
        return pHead;
    }
}
複製代碼

19. 正則表達式匹配

NowCoder

題目描述

請實現一個函數用來匹配包括 '.' 和 '*' 的正則表達式。模式中的字符 '.' 表示任意一個字符,而 '*' 表示它前面的字符能夠出現任意次(包含 0 次)。

在本題中,匹配是指字符串的全部字符匹配整個模式。例如,字符串 "aaa" 與模式 "a.a" 和 "ab*ac*a" 匹配,可是與 "aa.a" 和 "ab*a" 均不匹配。

解題思路

應該注意到,'.' 是用來當作一個任意字符,而 '*' 是用來重複前面的字符。這兩個的做用不一樣,不能把 '.' 的做用和 '*' 進行類比,從而把它當成重複前面字符一次。

public boolean match(char[] str, char[] pattern) {

    int m = str.length, n = pattern.length;
    boolean[][] dp = new boolean[m + 1][n + 1];

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

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

    return dp[m][n];
}
複製代碼

20. 表示數值的字符串

NowCoder

題目描述

true

"+100"
"5e2"
"-123"
"3.1416"
"-1E-16"
複製代碼
false

"12e"
"1a3.14"
"1.2.3"
"+-5"
"12e+4.3"
複製代碼

解題思路

使用正則表達式進行匹配。

[]  : 字符集合
()  : 分組
?   : 重複 0 ~ 1 次
+   : 重複 1 ~ n 次
*   : 重複 0 ~ n 次
.   : 任意字符
\\. : 轉義後的 .
\\d : 數字
複製代碼
public boolean isNumeric(char[] str) {
    if (str == null || str.length == 0)
        return false;
    return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
複製代碼

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

NowCoder

題目描述

須要保證奇數和奇數,偶數和偶數之間的相對位置不變,這和書本不太同樣。

解題思路

方法一:建立一個新數組,時間複雜度 O(N),空間複雜度 O(N)。

public void reOrderArray(int[] nums) {
    // 奇數個數
    int oddCnt = 0;
    for (int x : nums)
        if (!isEven(x))
            oddCnt++;
    int[] copy = nums.clone();
    int i = 0, j = oddCnt;
    for (int num : copy) {
        if (num % 2 == 1)
            nums[i++] = num;
        else
            nums[j++] = num;
    }
}

private boolean isEven(int x) {
    return x % 2 == 0;
}
複製代碼

方法二:使用冒泡思想,每次都當前偶數上浮到當前最右邊。時間複雜度 O(N2),空間複雜度 O(1),時間換空間。

public void reOrderArray(int[] nums) {
    int N = nums.length;
    for (int i = N - 1; i > 0; i--) {
        for (int j = 0; j < i; j++) {
            if (isEven(nums[j]) && !isEven(nums[j + 1])) {
                swap(nums, j, j + 1);
            }
        }
    }
}

private boolean isEven(int x) {
    return x % 2 == 0;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
複製代碼

22. 鏈表中倒數第 K 個結點

NowCoder

解題思路

設鏈表的長度爲 N。設置兩個指針 P1 和 P2,先讓 P1 移動 K 個節點,則還有 N - K 個節點能夠移動。此時讓 P1 和 P2 同時移動,能夠知道當 P1 移動到鏈表結尾時,P2 移動到第 N - K 個節點處,該位置就是倒數第 K 個節點。

public ListNode FindKthToTail(ListNode head, int k) {
    if (head == null)
        return null;
    ListNode P1 = head;
    while (P1 != null && k-- > 0)
        P1 = P1.next;
    if (k > 0)
        return null;
    ListNode P2 = head;
    while (P1 != null) {
        P1 = P1.next;
        P2 = P2.next;
    }
    return P2;
}
複製代碼

23. 鏈表中環的入口結點

NowCoder

題目描述

一個鏈表中包含環,請找出該鏈表的環的入口結點。要求不能使用額外的空間。

解題思路

使用雙指針,一個指針 fast 每次移動兩個節點,一個指針 slow 每次移動一個節點。由於存在環,因此兩個指針一定相遇在環中的某個節點上。假設相遇點在下圖的 z1 位置,此時 fast 移動的節點數爲 x+2y+z,slow 爲 x+y,因爲 fast 速度比 slow 快一倍,所以 x+2y+z=2(x+y),獲得 x=z。

在相遇點,slow 要到環的入口點還須要移動 z 個節點,若是讓 fast 從新從頭開始移動,而且速度變爲每次移動一個節點,那麼它到環入口點還須要移動 x 個節點。在上面已經推導出 x=z,所以 fast 和 slow 將在環入口點相遇。

public ListNode EntryNodeOfLoop(ListNode pHead) {
    if (pHead == null || pHead.next == null)
        return null;
    ListNode slow = pHead, fast = pHead;
    do {
        fast = fast.next.next;
        slow = slow.next;
    } while (slow != fast);
    fast = pHead;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
複製代碼

24. 反轉鏈表

NowCoder

解題思路

遞歸

public ListNode ReverseList(ListNode head) {
    if (head == null || head.next == null)
        return head;
    ListNode next = head.next;
    head.next = null;
    ListNode newHead = ReverseList(next);
    next.next = head;
    return newHead;
}
複製代碼

迭代

使用頭插法。

public ListNode ReverseList(ListNode head) {
    ListNode newList = new ListNode(-1);
    while (head != null) {
        ListNode next = head.next;
        head.next = newList.next;
        newList.next = head;
        head = next;
    }
    return newList.next;
}
複製代碼

25. 合併兩個排序的鏈表

NowCoder

題目描述

解題思路

遞歸

public ListNode Merge(ListNode list1, ListNode list2) {
    if (list1 == null)
        return list2;
    if (list2 == null)
        return list1;
    if (list1.val <= list2.val) {
        list1.next = Merge(list1.next, list2);
        return list1;
    } else {
        list2.next = Merge(list1, list2.next);
        return list2;
    }
}
複製代碼

迭代

public ListNode Merge(ListNode list1, ListNode list2) {
    ListNode head = new ListNode(-1);
    ListNode cur = head;
    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;
    }
    if (list1 != null)
        cur.next = list1;
    if (list2 != null)
        cur.next = list2;
    return head.next;
}
複製代碼

26. 樹的子結構

NowCoder

題目描述

解題思路

public boolean HasSubtree(TreeNode root1, TreeNode root2) {
    if (root1 == null || root2 == null)
        return false;
    return isSubtreeWithRoot(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
}

private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) {
    if (root2 == null)
        return true;
    if (root1 == null)
        return false;
    if (root1.val != root2.val)
        return false;
    return isSubtreeWithRoot(root1.left, root2.left) && isSubtreeWithRoot(root1.right, root2.right);
}
複製代碼

27. 二叉樹的鏡像

NowCoder

題目描述

解題思路

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

private void swap(TreeNode root) {
    TreeNode t = root.left;
    root.left = root.right;
    root.right = t;
}
複製代碼

28 對稱的二叉樹

NowCoder

題目描述

解題思路

boolean isSymmetrical(TreeNode pRoot) {
    if (pRoot == null)
        return true;
    return isSymmetrical(pRoot.left, pRoot.right);
}

boolean isSymmetrical(TreeNode t1, TreeNode t2) {
    if (t1 == null && t2 == null)
        return true;
    if (t1 == null || t2 == null)
        return false;
    if (t1.val != t2.val)
        return false;
    return isSymmetrical(t1.left, t2.right) && isSymmetrical(t1.right, t2.left);
}
複製代碼

29. 順時針打印矩陣

NowCoder

題目描述

下圖的矩陣順時針打印結果爲:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10

解題思路

public ArrayList<Integer> printMatrix(int[][] matrix) {
    ArrayList<Integer> ret = new ArrayList<>();
    int r1 = 0, r2 = matrix.length - 1, c1 = 0, c2 = matrix[0].length - 1;
    while (r1 <= r2 && c1 <= c2) {
        for (int i = c1; i <= c2; i++)
            ret.add(matrix[r1][i]);
        for (int i = r1 + 1; i <= r2; i++)
            ret.add(matrix[i][c2]);
        if (r1 != r2)
            for (int i = c2 - 1; i >= c1; i--)
                ret.add(matrix[r2][i]);
        if (c1 != c2)
            for (int i = r2 - 1; i > r1; i--)
                ret.add(matrix[i][c1]);
        r1++; r2--; c1++; c2--;
    }
    return ret;
}
複製代碼

30. 包含 min 函數的棧

NowCoder

題目描述

定義棧的數據結構,請在該類型中實現一個可以獲得棧最小元素的 min 函數。

解題思路

private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();

public void push(int node) {
    dataStack.push(node);
    minStack.push(minStack.isEmpty() ? node : Math.min(minStack.peek(), node));
}

public void pop() {
    dataStack.pop();
    minStack.pop();
}

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

public int min() {
    return minStack.peek();
}
複製代碼

31. 棧的壓入、彈出序列

NowCoder

題目描述

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否爲該棧的彈出順序。假設壓入棧的全部數字均不相等。

例如序列 1,2,3,4,5 是某棧的壓入順序,序列 4,5,3,2,1 是該壓棧序列對應的一個彈出序列,但 4,3,5,1,2 就不多是該壓棧序列的彈出序列。

解題思路

使用一個棧來模擬壓入彈出操做。

public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
    int n = pushSequence.length;
    Stack<Integer> stack = new Stack<>();
    for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
        stack.push(pushSequence[pushIndex]);
        while (popIndex < n && !stack.isEmpty() 
                && stack.peek() == popSequence[popIndex]) {
            stack.pop();
            popIndex++;
        }
    }
    return stack.isEmpty();
}
複製代碼

32.1 從上往下打印二叉樹

NowCoder

題目描述

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

例如,如下二叉樹層次遍歷的結果爲:1,2,3,4,5,6,7

解題思路

使用隊列來進行層次遍歷。

不須要使用兩個隊列分別存儲當前層的節點和下一層的節點,由於在開始遍歷一層的節點時,當前隊列中的節點數就是當前層的節點數,只要控制遍歷這麼多節點數,就能保證此次遍歷的都是當前層的節點。

public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    ArrayList<Integer> ret = new ArrayList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        int cnt = queue.size();
        while (cnt-- > 0) {
            TreeNode t = queue.poll();
            if (t == null)
                continue;
            ret.add(t.val);
            queue.add(t.left);
            queue.add(t.right);
        }
    }
    return ret;
}
複製代碼

32.2 把二叉樹打印成多行

NowCoder

題目描述

和上題幾乎同樣。

解題思路

ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(pRoot);
    while (!queue.isEmpty()) {
        ArrayList<Integer> list = new ArrayList<>();
        int cnt = queue.size();
        while (cnt-- > 0) {
            TreeNode node = queue.poll();
            if (node == null)
                continue;
            list.add(node.val);
            queue.add(node.left);
            queue.add(node.right);
        }
        if (list.size() != 0)
            ret.add(list);
    }
    return ret;
}
複製代碼

32.3 按之字形順序打印二叉樹

NowCoder

題目描述

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

解題思路

public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
    ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(pRoot);
    boolean reverse = false;
    while (!queue.isEmpty()) {
        ArrayList<Integer> list = new ArrayList<>();
        int cnt = queue.size();
        while (cnt-- > 0) {
            TreeNode node = queue.poll();
            if (node == null)
                continue;
            list.add(node.val);
            queue.add(node.left);
            queue.add(node.right);
        }
        if (reverse)
            Collections.reverse(list);
        reverse = !reverse;
        if (list.size() != 0)
            ret.add(list);
    }
    return ret;
}
複製代碼

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

NowCoder

題目描述

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

例如,下圖是後序遍歷序列 1,3,2 所對應的二叉搜索樹。

解題思路

public boolean VerifySquenceOfBST(int[] sequence) {
    if (sequence == null || sequence.length == 0)
        return false;
    return verify(sequence, 0, sequence.length - 1);
}

private boolean verify(int[] sequence, int first, int last) {
    if (last - first <= 1)
        return true;
    int rootVal = sequence[last];
    int cutIndex = first;
    while (cutIndex < last && sequence[cutIndex] <= rootVal)
        cutIndex++;
    for (int i = cutIndex; i < last; i++)
        if (sequence[i] < rootVal)
            return false;
    return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
}
複製代碼

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

NowCoder

題目描述

輸入一顆二叉樹和一個整數,打印出二叉樹中結點值的和爲輸入整數的全部路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所通過的結點造成一條路徑。

下圖的二叉樹有兩條和爲 22 的路徑:10, 5, 7 和 10, 12

解題思路

private ArrayList<ArrayList<Integer>> ret = new ArrayList<>();

public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
    backtracking(root, target, new ArrayList<>());
    return ret;
}

private void backtracking(TreeNode node, int target, ArrayList<Integer> path) {
    if (node == null)
        return;
    path.add(node.val);
    target -= node.val;
    if (target == 0 && node.left == null && node.right == null) {
        ret.add(new ArrayList<>(path));
    } else {
        backtracking(node.left, target, path);
        backtracking(node.right, target, path);
    }
    path.remove(path.size() - 1);
}
複製代碼

35. 複雜鏈表的複製

NowCoder

題目描述

輸入一個複雜鏈表(每一個節點中有節點值,以及兩個指針,一個指向下一個節點,另外一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的 head。

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

    RandomListNode(int label) {
        this.label = label;
    }
}
複製代碼

解題思路

第一步,在每一個節點的後面插入複製的節點。

第二步,對複製節點的 random 連接進行賦值。

第三步,拆分。

public RandomListNode Clone(RandomListNode pHead) {
    if (pHead == null)
        return null;
    // 插入新節點
    RandomListNode cur = pHead;
    while (cur != null) {
        RandomListNode clone = new RandomListNode(cur.label);
        clone.next = cur.next;
        cur.next = clone;
        cur = clone.next;
    }
    // 創建 random 連接
    cur = pHead;
    while (cur != null) {
        RandomListNode clone = cur.next;
        if (cur.random != null)
            clone.random = cur.random.next;
        cur = clone.next;
    }
    // 拆分
    cur = pHead;
    RandomListNode pCloneHead = pHead.next;
    while (cur.next != null) {
        RandomListNode next = cur.next;
        cur.next = next.next;
        cur = next;
    }
    return pCloneHead;
}
複製代碼

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

NowCoder

題目描述

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

解題思路

private TreeNode pre = null;
private TreeNode head = null;

public TreeNode Convert(TreeNode root) {
    inOrder(root);
    return head;
}

private void inOrder(TreeNode node) {
    if (node == null)
        return;
    inOrder(node.left);
    node.left = pre;
    if (pre != null)
        pre.right = node;
    pre = node;
    if (head == null)
        head = node;
    inOrder(node.right);
}
複製代碼

37. 序列化二叉樹

NowCoder

題目描述

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

解題思路

private String deserializeStr;

public String Serialize(TreeNode root) {
    if (root == null)
        return "#";
    return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
}

public TreeNode Deserialize(String str) {
    deserializeStr = str;
    return Deserialize();
}

private TreeNode Deserialize() {
    if (deserializeStr.length() == 0)
        return null;
    int index = deserializeStr.indexOf(" ");
    String node = index == -1 ? deserializeStr : deserializeStr.substring(0, index);
    deserializeStr = index == -1 ? "" : deserializeStr.substring(index + 1);
    if (node.equals("#"))
        return null;
    int val = Integer.valueOf(node);
    TreeNode t = new TreeNode(val);
    t.left = Deserialize();
    t.right = Deserialize();
    return t;
}
複製代碼

38. 字符串的排列

NowCoder

題目描述

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

解題思路

private ArrayList<String> ret = new ArrayList<>();

public ArrayList<String> Permutation(String str) {
    if (str.length() == 0)
        return ret;
    char[] chars = str.toCharArray();
    Arrays.sort(chars);
    backtracking(chars, new boolean[chars.length], new StringBuilder());
    return ret;
}

private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) {
    if (s.length() == chars.length) {
        ret.add(s.toString());
        return;
    }
    for (int i = 0; i < chars.length; i++) {
        if (hasUsed[i])
            continue;
        if (i != 0 && chars[i] == chars[i - 1] && !hasUsed[i - 1]) /* 保證不重複 */
            continue;
        hasUsed[i] = true;
        s.append(chars[i]);
        backtracking(chars, hasUsed, s);
        s.deleteCharAt(s.length() - 1);
        hasUsed[i] = false;
    }
}
複製代碼

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

NowCoder

解題思路

多數投票問題,能夠利用 Boyer-Moore Majority Vote Algorithm 來解決這個問題,使得時間複雜度爲 O(N)。

使用 cnt 來統計一個元素出現的次數,當遍歷到的元素和統計元素相等時,令 cnt++,不然令 cnt--。若是前面查找了 i 個元素,且 cnt == 0,說明前 i 個元素沒有 majority,或者有 majority,可是出現的次數少於 i / 2 ,由於若是多於 i / 2 的話 cnt 就必定不會爲 0 。此時剩下的 n - i 個元素中,majority 的數目依然多於 (n - i) / 2,所以繼續查找就能找出 majority。

public int MoreThanHalfNum_Solution(int[] nums) {
    int majority = nums[0];
    for (int i = 1, cnt = 1; i < nums.length; i++) {
        cnt = nums[i] == majority ? cnt + 1 : cnt - 1;
        if (cnt == 0) {
            majority = nums[i];
            cnt = 1;
        }
    }
    int cnt = 0;
    for (int val : nums)
        if (val == majority)
            cnt++;
    return cnt > nums.length / 2 ? majority : 0;
}
複製代碼

40. 最小的 K 個數

NowCoder

解題思路

快速選擇

  • 複雜度:O(N) + O(1)
  • 只有當容許修改數組元素時纔可使用

快速排序的 partition() 方法,會返回一個整數 j 使得 a[l..j-1] 小於等於 a[j],且 a[j+1..h] 大於等於 a[j],此時 a[j] 就是數組的第 j 大元素。能夠利用這個特性找出數組的第 K 個元素,這種找第 K 個元素的算法稱爲快速選擇算法。

public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (k > nums.length || k <= 0)
        return ret;
    findKthSmallest(nums, k - 1);
    /* findKthSmallest 會改變數組,使得前 k 個數都是最小的 k 個數 */
    for (int i = 0; i < k; i++)
        ret.add(nums[i]);
    return ret;
}

public void findKthSmallest(int[] nums, int k) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = partition(nums, l, h);
        if (j == k)
            break;
        if (j > k)
            h = j - 1;
        else
            l = j + 1;
    }
}

private int partition(int[] nums, int l, int h) {
    int p = nums[l];     /* 切分元素 */
    int i = l, j = h + 1;
    while (true) {
        while (i != h && nums[++i] < p) ;
        while (j != l && nums[--j] > p) ;
        if (i >= j)
            break;
        swap(nums, i, j);
    }
    swap(nums, l, j);
    return j;
}

private void swap(int[] nums, int i, int j) {
    int t = nums[i];
    nums[i] = nums[j];
    nums[j] = t;
}
複製代碼

大小爲 K 的最小堆

  • 複雜度:O(NlogK) + O(K)
  • 特別適合處理海量數據

應該使用大頂堆來維護最小堆,而不能直接建立一個小頂堆並設置一個大小,企圖讓小頂堆中的元素都是最小元素。

維護一個大小爲 K 的最小堆過程以下:在添加一個元素以後,若是大頂堆的大小大於 K,那麼須要將大頂堆的堆頂元素去除。

public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
    if (k > nums.length || k <= 0)
        return new ArrayList<>();
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
    for (int num : nums) {
        maxHeap.add(num);
        if (maxHeap.size() > k)
            maxHeap.poll();
    }
    return new ArrayList<>(maxHeap);
}
複製代碼

41.1 數據流中的中位數

NowCoder

題目描述

如何獲得一個數據流中的中位數?若是從數據流中讀出奇數個數值,那麼中位數就是全部數值排序以後位於中間的數值。若是從數據流中讀出偶數個數值,那麼中位數就是全部數值排序以後中間兩個數的平均值。

解題思路

/* 大頂堆,存儲左半邊元素 */
private PriorityQueue<Integer> left = new PriorityQueue<>((o1, o2) -> o2 - o1);
/* 小頂堆,存儲右半邊元素,而且右半邊元素都大於左半邊 */
private PriorityQueue<Integer> right = new PriorityQueue<>();
/* 當前數據流讀入的元素個數 */
private int N = 0;

public void Insert(Integer val) {
    /* 插入要保證兩個堆存於平衡狀態 */
    if (N % 2 == 0) {
        /* N 爲偶數的狀況下插入到右半邊。 * 由於右半邊元素都要大於左半邊,可是新插入的元素不必定比左半邊元素來的大, * 所以須要先將元素插入左半邊,而後利用左半邊爲大頂堆的特色,取出堆頂元素即爲最大元素,此時插入右半邊 */
        left.add(val);
        right.add(left.poll());
    } else {
        right.add(val);
        left.add(right.poll());
    }
    N++;
}

public Double GetMedian() {
    if (N % 2 == 0)
        return (left.peek() + right.peek()) / 2.0;
    else
        return (double) right.peek();
}
複製代碼

41.2 字符流中第一個不重複的字符

NowCoder

題目描述

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

解題思路

private int[] cnts = new int[256];
private Queue<Character> queue = new LinkedList<>();

public void Insert(char ch) {
    cnts[ch]++;
    queue.add(ch);
    while (!queue.isEmpty() && cnts[queue.peek()] > 1)
        queue.poll();
}

public char FirstAppearingOnce() {
    return queue.isEmpty() ? '#' : queue.peek();
}
複製代碼

42. 連續子數組的最大和

NowCoder

題目描述

{6, -3, -2, 7, -15, 1, 2, 2},連續子數組的最大和爲 8(從第 0 個開始,到第 3 個爲止)。

解題思路

public int FindGreatestSumOfSubArray(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    int greatestSum = Integer.MIN_VALUE;
    int sum = 0;
    for (int val : nums) {
        sum = sum <= 0 ? val : sum + val;
        greatestSum = Math.max(greatestSum, sum);
    }
    return greatestSum;
}
複製代碼

43. 從 1 到 n 整數中 1 出現的次數

NowCoder

解題思路

public int NumberOf1Between1AndN_Solution(int n) {
    int cnt = 0;
    for (int m = 1; m <= n; m *= 10) {
        int a = n / m, b = n % m;
        cnt += (a + 8) / 10 * m + (a % 10 == 1 ? b + 1 : 0);
    }
    return cnt;
}
複製代碼

Leetcode : 233. Number of Digit One

44. 數字序列中的某一位數字

題目描述

數字以 0123456789101112131415... 的格式序列化到一個字符串中,求這個字符串的第 index 位。

解題思路

public int getDigitAtIndex(int index) {
    if (index < 0)
        return -1;
    int place = 1;  // 1 表示個位,2 表示 十位...
    while (true) {
        int amount = getAmountOfPlace(place);
        int totalAmount = amount * place;
        if (index < totalAmount)
            return getDigitAtIndex(index, place);
        index -= totalAmount;
        place++;
    }
}

/** * place 位數的數字組成的字符串長度 * 10, 90, 900, ... */
private int getAmountOfPlace(int place) {
    if (place == 1)
        return 10;
    return (int) Math.pow(10, place - 1) * 9;
}

/** * place 位數的起始數字 * 0, 10, 100, ... */
private int getBeginNumberOfPlace(int place) {
    if (place == 1)
        return 0;
    return (int) Math.pow(10, place - 1);
}

/** * 在 place 位數組成的字符串中,第 index 個數 */
private int getDigitAtIndex(int index, int place) {
    int beginNumber = getBeginNumberOfPlace(place);
    int shiftNumber = index / place;
    String number = (beginNumber + shiftNumber) + "";
    int count = index % place;
    return number.charAt(count) - '0';
}
複製代碼

45. 把數組排成最小的數

NowCoder

題目描述

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

解題思路

能夠當作是一個排序問題,在比較兩個字符串 S1 和 S2 的大小時,應該比較的是 S1+S2 和 S2+S1 的大小,若是 S1+S2 < S2+S1,那麼應該把 S1 排在前面,不然應該把 S2 排在前面。

public String PrintMinNumber(int[] numbers) {
    if (numbers == null || numbers.length == 0)
        return "";
    int n = numbers.length;
    String[] nums = new String[n];
    for (int i = 0; i < n; i++)
        nums[i] = numbers[i] + "";
    Arrays.sort(nums, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
    String ret = "";
    for (String str : nums)
        ret += str;
    return ret;
}
複製代碼

46. 把數字翻譯成字符串

Leetcode

題目描述

給定一個數字,按照以下規則翻譯成字符串:1 翻譯成「a」,2 翻譯成「b」... 26 翻譯成「z」。一個數字有多種翻譯可能,例如 12258 一共有 5 種,分別是 abbeh,lbeh,aveh,abyh,lyh。實現一個函數,用來計算一個數字有多少種不一樣的翻譯方法。

解題思路

public int numDecodings(String s) {
    if (s == null || s.length() == 0)
        return 0;
    int n = s.length();
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = s.charAt(0) == '0' ? 0 : 1;
    for (int i = 2; i <= n; i++) {
        int one = Integer.valueOf(s.substring(i - 1, i));
        if (one != 0)
            dp[i] += dp[i - 1];
        if (s.charAt(i - 2) == '0')
            continue;
        int two = Integer.valueOf(s.substring(i - 2, i));
        if (two <= 26)
            dp[i] += dp[i - 2];
    }
    return dp[n];
}
複製代碼

47. 禮物的最大價值

NowCoder

題目描述

在一個 m*n 的棋盤的每個格都放有一個禮物,每一個禮物都有必定價值(大於 0)。從左上角開始拿禮物,每次向右或向下移動一格,直到右下角結束。給定一個棋盤,求拿到禮物的最大價值。例如,對於以下棋盤

1    10   3    8
12   2    9    6
5    7    4    11
3    7    16   5
複製代碼

禮物的最大價值爲 1+12+5+7+7+16+5=53。

解題思路

應該用動態規劃求解,而不是深度優先搜索,深度優先搜索過於複雜,不是最優解。

public int getMost(int[][] values) {
    if (values == null || values.length == 0 || values[0].length == 0)
        return 0;
    int n = values[0].length;
    int[] dp = new int[n];
    for (int[] value : values) {
        dp[0] += value[0];
        for (int i = 1; i < n; i++)
            dp[i] = Math.max(dp[i], dp[i - 1]) + value[i];
    }
    return dp[n - 1];
}
複製代碼

48. 最長不含重複字符的子字符串

題目描述

輸入一個字符串(只包含 a~z 的字符),求其最長不含重複字符的子字符串的長度。例如對於 arabcacfr,最長不含重複字符的子字符串爲 acfr,長度爲 4。

解題思路

public int longestSubStringWithoutDuplication(String str) {
    int curLen = 0;
    int maxLen = 0;
    int[] preIndexs = new int[26];
    Arrays.fill(preIndexs, -1);
    for (int curI = 0; curI < str.length(); curI++) {
        int c = str.charAt(curI) - 'a';
        int preI = preIndexs[c];
        if (preI == -1 || curI - preI > curLen) {
            curLen++;
        } else {
            maxLen = Math.max(maxLen, curLen);
            curLen = curI - preI;
        }
        preIndexs[c] = curI;
    }
    maxLen = Math.max(maxLen, curLen);
    return maxLen;
}
複製代碼

49. 醜數

NowCoder

題目描述

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

解題思路

public int GetUglyNumber_Solution(int N) {
    if (N <= 6)
        return N;
    int i2 = 0, i3 = 0, i5 = 0;
    int[] dp = new int[N];
    dp[0] = 1;
    for (int i = 1; i < N; i++) {
        int next2 = dp[i2] * 2, next3 = dp[i3] * 3, next5 = dp[i5] * 5;
        dp[i] = Math.min(next2, Math.min(next3, next5));
        if (dp[i] == next2)
            i2++;
        if (dp[i] == next3)
            i3++;
        if (dp[i] == next5)
            i5++;
    }
    return dp[N - 1];
}
複製代碼

50. 第一個只出現一次的字符位置

NowCoder

題目描述

在一個字符串中找到第一個只出現一次的字符,並返回它的位置。

Input: abacc
Output: b
複製代碼

解題思路

最直觀的解法是使用 HashMap 對出現次數進行統計,可是考慮到要統計的字符範圍有限,所以可使用整型數組代替 HashMap,從而將空間複雜度由 O(N) 下降爲 O(1)。

public int FirstNotRepeatingChar(String str) {
    int[] cnts = new int[256];
    for (int i = 0; i < str.length(); i++)
        cnts[str.charAt(i)]++;
    for (int i = 0; i < str.length(); i++)
        if (cnts[str.charAt(i)] == 1)
            return i;
    return -1;
}
複製代碼

以上實現的空間複雜度還不是最優的。考慮到只須要找到只出現一次的字符,那麼須要統計的次數信息只有 0,1,更大,使用兩個比特位就能存儲這些信息。

public int FirstNotRepeatingChar2(String str) {
    BitSet bs1 = new BitSet(256);
    BitSet bs2 = new BitSet(256);
    for (char c : str.toCharArray()) {
        if (!bs1.get(c) && !bs2.get(c))
            bs1.set(c);     // 0 0 -> 0 1
        else if (bs1.get(c) && !bs2.get(c))
            bs2.set(c);     // 0 1 -> 1 1
    }
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (bs1.get(c) && !bs2.get(c))  // 0 1
            return i;
    }
    return -1;
}
複製代碼

51. 數組中的逆序對

NowCoder

題目描述

在數組中的兩個數字,若是前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數。

解題思路

private long cnt = 0;
private int[] tmp;  // 在這裏聲明輔助數組,而不是在 merge() 遞歸函數中聲明

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

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

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

52. 兩個鏈表的第一個公共結點

NowCoder

題目描述

解題思路

設 A 的長度爲 a + c,B 的長度爲 b + c,其中 c 爲尾部公共部分長度,可知 a + c + b = b + c + a。

當訪問鏈表 A 的指針訪問到鏈表尾部時,令它從鏈表 B 的頭部從新開始訪問鏈表 B;一樣地,當訪問鏈表 B 的指針訪問到鏈表尾部時,令它從鏈表 A 的頭部從新開始訪問鏈表 A。這樣就能控制訪問 A 和 B 兩個鏈表的指針能同時訪問到交點。

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    ListNode l1 = pHead1, l2 = pHead2;
    while (l1 != l2) {
        l1 = (l1 == null) ? pHead2 : l1.next;
        l2 = (l2 == null) ? pHead1 : l2.next;
    }
    return l1;
}
複製代碼

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

NowCoder

題目描述

Input:
nums = 1, 2, 3, 3, 3, 3, 4, 6
K = 3

Output:
4
複製代碼

解題思路

public int GetNumberOfK(int[] nums, int K) {
    int first = binarySearch(nums, K);
    int last = binarySearch(nums, K + 1);
    return (first == nums.length || nums[first] != K) ? 0 : last - first;
}

private int binarySearch(int[] nums, int K) {
    int l = 0, h = nums.length;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] >= K)
            h = m;
        else
            l = m + 1;
    }
    return l;
}
複製代碼

54. 二叉查找樹的第 K 個結點

NowCoder

解題思路

利用二叉查找樹中序遍歷有序的特色。

private TreeNode ret;
private int cnt = 0;

public TreeNode KthNode(TreeNode pRoot, int k) {
    inOrder(pRoot, k);
    return ret;
}

private void inOrder(TreeNode root, int k) {
    if (root == null || cnt >= k)
        return;
    inOrder(root.left, k);
    cnt++;
    if (cnt == k)
        ret = root;
    inOrder(root.right, k);
}
複製代碼

55.1 二叉樹的深度

NowCoder

題目描述

從根結點到葉結點依次通過的結點(含根、葉結點)造成樹的一條路徑,最長路徑的長度爲樹的深度。

解題思路

public int TreeDepth(TreeNode root) {
    return root == null ? 0 : 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
}
複製代碼

55.2 平衡二叉樹

NowCoder

題目描述

平衡二叉樹左右子樹高度差不超過 1。

解題思路

private boolean isBalanced = true;

public boolean IsBalanced_Solution(TreeNode root) {
    height(root);
    return isBalanced;
}

private int height(TreeNode root) {
    if (root == null || !isBalanced)
        return 0;
    int left = height(root.left);
    int right = height(root.right);
    if (Math.abs(left - right) > 1)
        isBalanced = false;
    return 1 + Math.max(left, right);
}
複製代碼

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

NowCoder

題目描述

一個整型數組裏除了兩個數字以外,其餘的數字都出現了兩次,找出這兩個數。

解題思路

兩個不相等的元素在位級表示上一定會有一位存在不一樣,將數組的全部元素異或獲得的結果爲不存在重複的兩個元素異或的結果。

diff &= -diff 獲得出 diff 最右側不爲 0 的位,也就是不存在重複的兩個元素在位級表示上最右側不一樣的那一位,利用這一位就能夠將兩個元素區分開來。

public void FindNumsAppearOnce(int[] nums, int num1[], int num2[]) {
    int diff = 0;
    for (int num : nums)
        diff ^= num;
    diff &= -diff;
    for (int num : nums) {
        if ((num & diff) == 0)
            num1[0] ^= num;
        else
            num2[0] ^= num;
    }
}
複製代碼

57.1 和爲 S 的兩個數字

NowCoder

題目描述

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

解題思路

使用雙指針,一個指針指向元素較小的值,一個指針指向元素較大的值。指向較小元素的指針從頭向尾遍歷,指向較大元素的指針從尾向頭遍歷。

  • 若是兩個指針指向元素的和 sum == target,那麼獲得要求的結果;
  • 若是 sum > target,移動較大的元素,使 sum 變小一些;
  • 若是 sum < target,移動較小的元素,使 sum 變大一些。
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
    int i = 0, j = array.length - 1;
    while (i < j) {
        int cur = array[i] + array[j];
        if (cur == sum)
            return new ArrayList<>(Arrays.asList(array[i], array[j]));
        if (cur < sum)
            i++;
        else
            j--;
    }
    return new ArrayList<>();
}
複製代碼

57.2 和爲 S 的連續正數序列

NowCoder

題目描述

輸出全部和爲 S 的連續正數序列。

例如和爲 100 的連續序列有:

[9, 10, 11, 12, 13, 14, 15, 16]
[18, 19, 20, 21, 22]。
複製代碼

解題思路

public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    ArrayList<ArrayList<Integer>> ret = new ArrayList<>();
    int start = 1, end = 2;
    int curSum = 3;
    while (end < sum) {
        if (curSum > sum) {
            curSum -= start;
            start++;
        } else if (curSum < sum) {
            end++;
            curSum += end;
        } else {
            ArrayList<Integer> list = new ArrayList<>();
            for (int i = start; i <= end; i++)
                list.add(i);
            ret.add(list);
            curSum -= start;
            start++;
            end++;
            curSum += end;
        }
    }
    return ret;
}
複製代碼

58.1 翻轉單詞順序列

NowCoder

題目描述

Input:
"I am a student."

Output:
"student. a am I"
複製代碼

解題思路

題目應該有一個隱含條件,就是不能用額外的空間。雖然 Java 的題目輸入參數爲 String 類型,須要先建立一個字符數組使得空間複雜度爲 O(N),可是正確的參數類型應該和原書同樣,爲字符數組,而且只能使用該字符數組的空間。任何使用了額外空間的解法在面試時都會大打折扣,包括遞歸解法。

正確的解法應該是和書上同樣,先旋轉每一個單詞,再旋轉整個字符串。

public String ReverseSentence(String str) {
    int n = str.length();
    char[] chars = str.toCharArray();
    int i = 0, j = 0;
    while (j <= n) {
        if (j == n || chars[j] == ' ') {
            reverse(chars, i, j - 1);
            i = j + 1;
        }
        j++;
    }
    reverse(chars, 0, n - 1);
    return new String(chars);
}

private void reverse(char[] c, int i, int j) {
    while (i < j)
        swap(c, i++, j--);
}

private void swap(char[] c, int i, int j) {
    char t = c[i];
    c[i] = c[j];
    c[j] = t;
}
複製代碼

58.2 左旋轉字符串

NowCoder

題目描述

Input:
S="abcXYZdef"
K=3

Output:
"XYZdefabc"
複製代碼

解題思路

先將 "abc" 和 "XYZdef" 分別翻轉,獲得 "cbafedZYX",而後再把整個字符串翻轉獲得 "XYZdefabc"。

public String LeftRotateString(String str, int n) {
    if (n >= str.length())
        return str;
    char[] chars = str.toCharArray();
    reverse(chars, 0, n - 1);
    reverse(chars, n, chars.length - 1);
    reverse(chars, 0, chars.length - 1);
    return new String(chars);
}

private void reverse(char[] chars, int i, int j) {
    while (i < j)
        swap(chars, i++, j--);
}

private void swap(char[] chars, int i, int j) {
    char t = chars[i];
    chars[i] = chars[j];
    chars[j] = t;
}
複製代碼

59. 滑動窗口的最大值

NowCoder

題目描述

給定一個數組和滑動窗口的大小,找出全部滑動窗口裏數值的最大值。

例如,若是輸入數組 {2, 3, 4, 2, 6, 2, 5, 1} 及滑動窗口的大小 3,那麼一共存在 6 個滑動窗口,他們的最大值分別爲 {4, 4, 6, 6, 6, 5}。

解題思路

public ArrayList<Integer> maxInWindows(int[] num, int size) {
    ArrayList<Integer> ret = new ArrayList<>();
    if (size > num.length || size < 1)
        return ret;
    PriorityQueue<Integer> heap = new PriorityQueue<>((o1, o2) -> o2 - o1);  /* 大頂堆 */
    for (int i = 0; i < size; i++)
        heap.add(num[i]);
    ret.add(heap.peek());
    for (int i = 0, j = i + size; j < num.length; i++, j++) {            /* 維護一個大小爲 size 的大頂堆 */
        heap.remove(num[i]);
        heap.add(num[j]);
        ret.add(heap.peek());
    }
    return ret;
}
複製代碼

60. n 個骰子的點數

Lintcode

題目描述

把 n 個骰子仍在地上,求點數和爲 s 的機率。

解題思路

動態規劃

使用一個二維數組 dp 存儲點數出現的次數,其中 dp[i][j] 表示前 i 個骰子產生點數 j 的次數。

空間複雜度:O(N2)

public List<Map.Entry<Integer, Double>> dicesSum(int n) {
    final int face = 6;
    final int pointNum = face * n;
    long[][] dp = new long[n + 1][pointNum + 1];

    for (int i = 1; i <= face; i++)
        dp[1][i] = 1;

    for (int i = 2; i <= n; i++)
        for (int j = i; j <= pointNum; j++)     /* 使用 i 個骰子最小點數爲 i */
            for (int k = 1; k <= face && k <= j; k++)
                dp[i][j] += dp[i - 1][j - k];

    final double totalNum = Math.pow(6, n);
    List<Map.Entry<Integer, Double>> ret = new ArrayList<>();
    for (int i = n; i <= pointNum; i++)
        ret.add(new AbstractMap.SimpleEntry<>(i, dp[n][i] / totalNum));

    return ret;
}
複製代碼

動態規劃 + 旋轉數組

空間複雜度:O(N)

public List<Map.Entry<Integer, Double>> dicesSum(int n) {
    final int face = 6;
    final int pointNum = face * n;
    long[][] dp = new long[2][pointNum + 1];

    for (int i = 1; i <= face; i++)
        dp[0][i] = 1;

    int flag = 1;                                     /* 旋轉標記 */
    for (int i = 2; i <= n; i++, flag = 1 - flag) {
        for (int j = 0; j <= pointNum; j++)
            dp[flag][j] = 0;                          /* 旋轉數組清零 */

        for (int j = i; j <= pointNum; j++)
            for (int k = 1; k <= face && k <= j; k++)
                dp[flag][j] += dp[1 - flag][j - k];
    }

    final double totalNum = Math.pow(6, n);
    List<Map.Entry<Integer, Double>> ret = new ArrayList<>();
    for (int i = n; i <= pointNum; i++)
        ret.add(new AbstractMap.SimpleEntry<>(i, dp[1 - flag][i] / totalNum));

    return ret;
}
複製代碼

61. 撲克牌順子

NowCoder

題目描述

五張牌,其中大小鬼爲癩子,牌面爲 0。判斷這五張牌是否能組成順子。

解題思路

public boolean isContinuous(int[] nums) {

    if (nums.length < 5)
        return false;

    Arrays.sort(nums);

    // 統計癩子數量
    int cnt = 0;
    for (int num : nums)
        if (num == 0)
            cnt++;

    // 使用癩子去補全不連續的順子
    for (int i = cnt; i < nums.length - 1; i++) {
        if (nums[i + 1] == nums[i])
            return false;
        cnt -= nums[i + 1] - nums[i] - 1;
    }

    return cnt >= 0;
}
複製代碼

62. 圓圈中最後剩下的數

NowCoder

題目描述

讓小朋友們圍成一個大圈。而後,隨機指定一個數 m,讓編號爲 0 的小朋友開始報數。每次喊到 m-1 的那個小朋友要出列唱首歌,而後能夠在禮品箱中任意的挑選禮物,而且再也不回到圈中,從他的下一個小朋友開始,繼續 0...m-1 報數 .... 這樣下去 .... 直到剩下最後一個小朋友,能夠不用表演。

解題思路

約瑟夫環,圓圈長度爲 n 的解能夠當作長度爲 n-1 的解再加上報數的長度 m。由於是圓圈,因此最後須要對 n 取餘。

public int LastRemaining_Solution(int n, int m) {
    if (n == 0)     /* 特殊輸入的處理 */
        return -1;
    if (n == 1)     /* 遞歸返回條件 */
        return 0;
    return (LastRemaining_Solution(n - 1, m) + m) % n;
}
複製代碼

63. 股票的最大利潤

Leetcode

題目描述

能夠有一次買入和一次賣出,買入必須在前。求最大收益。

解題思路

使用貪心策略,假設第 i 輪進行賣出操做,買入操做價格應該在 i 以前而且價格最低。

public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0)
        return 0;
    int soFarMin = prices[0];
    int maxProfit = 0;
    for (int i = 1; i < prices.length; i++) {
        soFarMin = Math.min(soFarMin, prices[i]);
        maxProfit = Math.max(maxProfit, prices[i] - soFarMin);
    }
    return maxProfit;
}
複製代碼

64. 求 1+2+3+...+n

NowCoder

題目描述

要求不能使用乘除法、for、while、if、else、switch、case 等關鍵字及條件判斷語句 A ? B : C。

解題思路

使用遞歸解法最重要的是指定返回條件,可是本題沒法直接使用 if 語句來指定返回條件。

條件與 && 具備短路原則,即在第一個條件語句爲 false 的狀況下不會去執行第二個條件語句。利用這一特性,將遞歸的返回條件取非而後做爲 && 的第一個條件語句,遞歸的主體轉換爲第二個條件語句,那麼當遞歸的返回條件爲 true 的狀況下就不會執行遞歸的主體部分,遞歸返回。

本題的遞歸返回條件爲 n <= 0,取非後就是 n > 0;遞歸的主體部分爲 sum += Sum_Solution(n - 1),轉換爲條件語句後就是 (sum += Sum_Solution(n - 1)) > 0。

public int Sum_Solution(int n) {
    int sum = n;
    boolean b = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
    return sum;
}
複製代碼

65. 不用加減乘除作加法

NowCoder

題目描述

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

解題思路

a ^ b 表示沒有考慮進位的狀況下兩數的和,(a & b) << 1 就是進位。

遞歸會終止的緣由是 (a & b) << 1 最右邊會多一個 0,那麼繼續遞歸,進位最右邊的 0 會慢慢增多,最後進位會變爲 0,遞歸終止。

public int Add(int a, int b) {
    return b == 0 ? a : Add(a ^ b, (a & b) << 1);
}
複製代碼

66. 構建乘積數組

NowCoder

題目描述

給定一個數組 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 int[] multiply(int[] A) {
    int n = A.length;
    int[] B = new int[n];
    for (int i = 0, product = 1; i < n; product *= A[i], i++)       /* 從左往右累乘 */
        B[i] = product;
    for (int i = n - 1, product = 1; i >= 0; product *= A[i], i--)  /* 從右往左累乘 */
        B[i] *= product;
    return B;
}
複製代碼

67. 把字符串轉換成整數

NowCoder

題目描述

將一個字符串轉換成一個整數,字符串不是一個合法的數值則返回 0,要求不能使用字符串轉換整數的庫函數。

Iuput:
+2147483647
1a33

Output:
2147483647
0
複製代碼

解題思路

public int StrToInt(String str) {
    if (str == null || str.length() == 0)
        return 0;
    boolean isNegative = str.charAt(0) == '-';
    int ret = 0;
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (i == 0 && (c == '+' || c == '-'))  /* 符號斷定 */
            continue;
        if (c < '0' || c > '9')                /* 非法輸入 */
            return 0;
        ret = ret * 10 + (c - '0');
    }
    return isNegative ? -ret : ret;
}
複製代碼

68. 樹中兩個節點的最低公共祖先

解題思路

二叉查找樹

Leetcode : 235. Lowest Common Ancestor of a Binary Search Tree

二叉查找樹中,兩個節點 p, q 的公共祖先 root 知足 root.val >= p.val && root.val <= q.val。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null)
        return root;
    if (root.val > p.val && root.val > q.val)
        return lowestCommonAncestor(root.left, p, q);
    if (root.val < p.val && root.val < q.val)
        return lowestCommonAncestor(root.right, p, q);
    return root;
}
複製代碼

普通二叉樹

Leetcode : 236. Lowest Common Ancestor of a Binary Tree

在左右子樹中查找是否存在 p 或者 q,若是 p 和 q 分別在兩個子樹中,那麼就說明根節點就是最低公共祖先。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q)
        return root;
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    return left == null ? right : right == null ? left : root;
}
複製代碼
相關文章
相關標籤/搜索