劍指Offer第四章:解決面試題的思路

總結

  1. 想清楚再編碼
  2. 分析方法:舉例子、畫圖

第1節:畫圖分析方法

對于二叉樹、二維數組、鏈表等問題,都可以采用畫圖的方法來分析,例如:

  • 面試題19二叉樹的鏡像:通過畫圖發現,實質就是在遍歷樹的同時交換非葉節點的左右子節點。
  • 面試題20順時針打印矩陣:通過畫圖發現,實質就是一圈一圈的打印數組。
  • 面試題26復雜鏈表的復制:畫圖,發現復制鏈表的過程,分三個步驟:復制節點,設置random指針,拆分兩個鏈表。

面試題 19:二叉樹的鏡像(反轉二叉樹)

題目:請完成一個函數,輸入一個二叉樹,該函數輸出它的鏡像。

題目分析

LeetCode 226. Invert Binary Tree
何為鏡像:即照鏡子得到的像,與原像是左右顛倒的。
求二叉樹鏡像:即反轉二叉樹。
求解思路:對于每個非葉子節點,反轉其左右孩子節點。既可以用遞歸也可以用迭代。
題目典故:著名的Homebrew的作者 Max Howell在面試Google時被問到這題,并且沒有做出來,原推文

  • Google: 90% of our engineers use the software you wrote (Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.

題目考點及相關題目

問題本質:樹的DFS或BFS遍歷。
擴展:掌握非遞歸的實現。

我的代碼如下:

1.遞歸方法:

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

2.非遞歸DFS(棧):

public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        final Deque<TreeNode> stack = new LinkedList<>();
        stack.push(root);
        while(!stack.isEmpty()) {
            final TreeNode node = stack.pop();
            final TreeNode left = node.left;
            node.left = node.right;
            node.right = left;
            if(node.left != null) {
                stack.push(node.left);
            }
            if(node.right != null) {
                stack.push(node.right);
            }
        }
        return root;
    }

3.非遞歸BFS(隊列):

public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        final Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            final TreeNode node = queue.poll();
            final TreeNode left = node.left;
            node.left = node.right;
            node.right = left;
            if(node.left != null) {
                queue.offer(node.left);
            }
            if(node.right != null) {
                queue.offer(node.right);
            }
        }
        return root;
    }

面試題 20:順時針打印矩陣

題目:輸入一個矩陣,按照從外向里以順時針的順序依次打印出每一個數字。

題目分析

LeetCode 54. Spiral Matrix
順時針打印矩陣:即螺旋矩陣輸出。
規律:一圈一圈的輸出數組里的數據,注意邊界條件不好確定時,多畫幾個圖就很清楚了。
邊界: “上”肯定要打印,打印“右”的條件是至少有兩行,打印“下”至少兩行兩列;打印“左”至少要有三行兩列。

題目考點及相關題目

多畫圖,幫助理解。
相關題目有:LeetCode 59. Spiral Matrix II

我的代碼如下:
解法一:

public class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<Integer>();
        if(matrix.length <= 0) return res;
        int m = matrix.length, n = matrix[0].length;
        int min = Math.min(m, n);
        int k = min%2 == 0 ? (min/2 - 1) : min/2;
        for(int i = 0; i <= k; i ++)
           spiral(matrix, i, res, m, n);
        return res;
    }
    
    public void spiral(int[][] matrix, int k, List<Integer> res, int m, int n){
        
        // 上
        for(int i = k; i < n - k; i ++)
           res.add(matrix[k][i]);
        
        // 右
        for(int i = k + 1; i < m - k; i ++)
           res.add(matrix[i][n-k-1]);
           
        // 下(加判斷條件,排除兩種情況:只有一列時,只有一行時)
        if(k < n - k - 1 && k < m - k - 1){
            for(int i = n - k - 2; i >= k; i --)
                res.add(matrix[m-k-1][i]);
        }
           
        // 左(加判斷條件,排除兩種情況:只有一列時,只有不超過2行時)
        if(k < n - k - 1 && k < m - k - 2){
            for(int i = m - k - 2; i > k; i --)
               res.add(matrix[i][k]);
        }
    }
}

解法二:

public class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {

        List<Integer> res = new ArrayList<Integer>();

        if (matrix.length == 0) {
            return res;
        }

        int rowBegin = 0;
        int rowEnd = matrix.length-1;
        int colBegin = 0;
        int colEnd = matrix[0].length - 1;

        while (rowBegin <= rowEnd && colBegin <= colEnd) {
            // Traverse Right
            for (int j = colBegin; j <= colEnd; j ++) {
                res.add(matrix[rowBegin][j]);
            }
            rowBegin++;

            // Traverse Down
            for (int j = rowBegin; j <= rowEnd; j ++) {
                res.add(matrix[j][colEnd]);
            }
            colEnd--;

            if (rowBegin <= rowEnd) {
                // Traverse Left
                for (int j = colEnd; j >= colBegin; j --) {
                    res.add(matrix[rowEnd][j]);
                }
            }
            rowEnd--;

            if (colBegin <= colEnd) {
                // Traver Up
                for (int j = rowEnd; j >= rowBegin; j --) {
                    res.add(matrix[j][colBegin]);
                }
            }
            colBegin ++;
        }

        return res;
    }
}

第2節:舉例分析方法

通過舉例子,理解題目意思并找到規律;最后也以用例子來測試程序是否完善。

  • 面試題22棧的壓入、彈出序列:通過舉實際棧的例子,來模擬棧的壓入和彈出操作,就能發現隱藏的規律。
  • 面試題24二叉搜索樹的后序遍歷序列:理解后續遍歷的特點,并找到遞歸方法的思路。
  • 面試題21包含min函數的棧:用一個棧來專門來存儲當前棧中的最小值。

面試題 21:包含min函數的棧

題目:定義棧的數據結構,請在該類型中實現一個能夠得到棧的最小元素的min函數。在該函數中,調用min、push、及pop的時間復雜度都是$O(1)$。

題目分析

LeetCode 155. Min Stack

  1. 使用兩個棧,一個數據棧,一個最小數棧
  2. 每次存放數據時,若存放的數據比此時最小棧中的棧頂值要大,那么將最小數棧棧頂元素再存一次(即增加一個棧頂元素),如果要存的數據比棧頂元素小,那么就將此值也存入最小值棧。
  3. 出棧時,兩棧都要同時出數據,使得最小數棧的棧頂元素總是目前數據棧中的最小值。兩個棧中的元素個數總是保持相等。

題目考點及相關題目

用一個輔助棧來存儲最小值元素。
相關題目:LeetCode 239. Sliding Window Maximum

我的代碼如下:

class MinStack {
    
    Stack<Integer> data;
    Stack<Integer> min;
    
    public MinStack() {
        // do initialize if necessary
        data = new Stack<Integer>();
        min = new Stack<Integer>();
    }
    
    public void push(int x) {
        if (!min.isEmpty() && min.peek() < x) min.push(min.peek());
        else min.push(x);
        data.push(x);
    }

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

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

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

面試題 22:棧的壓入、彈出序列

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

題目分析

用一個輔助棧stack來存儲壓棧序列,如果下一個彈出的數字剛好是輔助棧頂數字,那么直接彈出,并將輔助棧內棧頂數字也彈出;如果下一個彈出的數字不在輔助棧頂,我們把壓棧序列中還沒有入棧的數字壓入輔助棧,直到下一個需要彈出的數字壓入棧頂為止。如果 所有的數字都 壓入棧了仍然沒有找到下一個彈出的數字,那么該序列不可能是一個彈出序列。

題目考點及相關題目

棧的壓入、彈出操作。

我的代碼如下:

    public boolean isPopOrder(List<Integer> push, List<Integer> pop){
        if(pop.size() != push.size()) return false;
        Stack<Integer> stack = new Stack<Integer>();
        while(!pop.isEmpty()){
            if(stack.isEmpty()) stack.push(push.remove(0));
            while(stack.peek() != pop.get(0) && !push.isEmpty()) stack.push(push.remove(0));
            if(push.isEmpty() && stack.peek() != pop.get(0)) return false;
            else{
                stack.pop();
                pop.remove(0);
            }
        }
        return true;
    }

面試題 23:從上往下打印二叉樹

題目:從上往下拓印出二叉樹的每個結點,同層的結點 按照從左到右的順序 打印。

題目分析

LeetCode 102. Binary Tree Level Order Traversal
即樹的層次遍歷,用到了隊列。

題目考點及相關題目

層次遍歷,隊列。
相關題目:LeetCode 103. Binary Tree Zigzag Level Order TraversalLeetCode 107. Binary Tree Level Order Traversal IILeetCode 111. Minimum Depth of Binary Tree
我的代碼如下:

public class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> rs = new ArrayList<List<Integer>>();
        LinkedList<TreeNode> level = new LinkedList<TreeNode>();
        if(root != null) level.push(root);
        while(!level.isEmpty()){
            int levelNum = level.size();
            List<Integer> tmp = new ArrayList<Integer>();
            for(int i=0; i<levelNum; i++){
                TreeNode temp = level.removeFirst();
                tmp.add(temp.val);
                if(temp.left != null) level.add(temp.left);
                if(temp.right != null) level.add(temp.right);
            }
            rs.add(tmp);
        }
        return rs;
    }
}

面試題 24:二叉搜索樹的后序遍歷序列

題目:輸入一個整數數組,判斷該數組是不是某二叉搜索樹的后序遍歷的結果。如果是則返回true,否則返回false。假設輸入的數組的任意兩個數字都互不相同。

題目分析

經過分析可知,后序遍歷得到的序列的特點:

  1. 最后一個數字是該二叉搜索樹的根節點,前面的序列又可以分成兩部分;
  2. 前一部分是根節點的左子樹,它們的值都比根節點值小;
  3. 后一部分是根節點的右子樹,它們的值都比根節點值大。

因此,需要再次判斷該二叉樹的左子樹序列和右子樹序列是否滿足以上特點。很明顯地,這是一個遞歸操作的過程。

題目考點及相關題目

二叉搜索樹的概念以及后序遍歷的特點
相關題目:輸入一個整數數組,判斷該數組是不是某二叉搜索樹的前序遍歷的結果。

我的代碼如下:

1.我的遞歸程序,沒有經過完整的校驗

public class Solution{
   public boolean VerifySequenceOfBST(int[] sequence){
      if(sequence.length <= 0) return false;
      return verify(sequence, 0, sequence.length - 1);
   }
   public boolean verify(int[] sequence, int start, int end){
      if(start >= end) return true;
      int root = sequence[end];
      int i = start, j = end - 1;
      // 找到左右子序列的分界處,經驗證,無論是只有左子樹還是只有右子樹,或者左右子樹均有的情況,都會滿足i == j + 1;否則說明該序列不滿足二叉搜索樹的性質。
      while(i < end && sequence[i] < root) i ++;
      while(j >= start && sequence[j] > root) j --;
      if(i != j + 1) return false;
      return verify(sequence, start, j) && verify(sequence, i, end - 1);
   }
}

2.標準答案請見P158

面試題 25:二叉樹中和為某一值的路徑

題目:輸入一棵二叉樹和一個整數,打印出二叉樹中結點值的和為輸入整數的所有路徑。從樹的根節點開始往下一直到葉子節點所經過的節點形成一條路徑。

題目分析

LeetCode 113. Path Sum II

  1. 首先這種題肯定是對樹遍歷,而樹的遍歷要么是深搜,要么是廣搜;
  2. 對于深搜,那就是遞歸了,關鍵問題在:怎么計算并存儲遍歷到當前節點時的和,可以用我的方法,用一個變量來記錄到達當前節點的和,或者方法二中比較巧妙的思想。
  3. 深搜遞歸總會遇到的問題是:變量(中間結果)在傳遞的過程中會發生改變,比如方法一中的:list和cur,一定要注意還原,這是非常重要的一步,否則會導致先前的結果仍然在list當中。這也是形參和實參的問題。
  4. 對于廣搜,那么就要用額外的變量存儲到達每個節點的和,這種方法的空間復雜度比較高,這里就不仔細介紹了。

題目考點及相關題目

樹的前序遍歷,也就是DFS
相關題目:Path SumBinary Tree Paths

我的代碼如下:

1.我的深搜代碼

public class Solution {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root == null) return res;
        pathSumWithDFS(root, root.val, new ArrayList<Integer>(Arrays.asList(root.val)), sum);
        return res;
    }
    
    public void pathSumWithDFS(TreeNode root, int cur, List<Integer> list, int sum){
        if(root.left == null && root.right == null){
            if(cur == sum) {
                // 這是十分推薦的寫法
                List<Integer> temp = new ArrayList<Integer>(list);
                res.add(temp);
            }
            return;
        }
        //if(cur >= sum) return; 注意這一句不能加,因為可能節點值有負值。
        if(root.left != null) {
            list.add(root.left.val);
            pathSumWithDFS(root.left, cur + root.left.val, list, sum);
            // 這一步非常重要
            list.remove(list.size()-1);
        }
        if(root.right != null) {
            list.add(root.right.val);
            pathSumWithDFS(root.right, cur + root.right.val, list, sum);
            // 這一步非常重要
            list.remove(list.size()-1);
        }
    }
}

2.他人更加簡潔的代碼(用棧更好,而且它這個函數不用記錄到目前節點的和,而是用sum-當前節點的值,方法更加巧妙)

public class Solution {
    private List<List<Integer>> resultList = new ArrayList<List<Integer>>();

    public void pathSumInner(TreeNode root, int sum, Stack<Integer>path) {
        path.push(root.val);
        if(root.left == null && root.right == null)
            if(sum == root.val) resultList.add(new ArrayList<Integer>(path));
        if(root.left!=null) pathSumInner(root.left, sum-root.val, path);
        if(root.right!=null)pathSumInner(root.right, sum-root.val, path);
        // 這一步非常重要
        path.pop();
    }

    public List<List<Integer>> pathSum(TreeNode root, int sum) {
        if(root==null) return resultList;
        Stack<Integer> path = new Stack<Integer>();
        pathSumInner(root, sum, path);
        return resultList;
    }
}

第3節:分解讓復雜問題簡單化

分治法:先把大問題分解成若干個小問題,然后再逐個解決的思想。

  • 面試題27二叉搜索樹與雙向鏈表:把大問題分解成左子樹和右子樹兩個小問題,然后再把轉換左右子樹得到的鏈表和根結點鏈接起來就解決了整個問題。
  • 面試題28字符串的排列:整個字符串的排列是一個大問題,而第一個字符之后的字符串的排列就是一個小問題,因此分解問題,并采用遞歸思想解決。

面試題 26:復雜鏈表的復制

題目:請實現函數ComplexListNode *Clone(ComplexListNode *pHead),復制一個復雜鏈表。在復雜鏈表中,每個結點除了有一個m_pNext指針指向下一個結點,還有一個m_pSibling指向的任意結點或者NULL。

題目分析

LeetCode 138. Copy List with Random Pointer

  1. 首先,要理解什么是深拷貝。
    -深拷貝是指在拷貝對象時,同時會對引用指向的對象進行拷貝。
    -相對應的,淺拷貝是指在拷貝對象時,對于基本數據類型的變量會重新復制一份,而對于引用類型的變量只是對引用進行拷貝。
  2. 此題深拷貝鏈表,主要分為如下兩步:
    -第一步復制原始鏈表中的結點,并用next指針連接起來。
    -第二步設置每個結點的random指針。
  3. 第一步很容易就可以完成,主要在于第二步,很容易就會犯淺拷貝的錯。只是復制了對象的引用,導致結果出錯。
  4. 第二步中復制random指針,應該是根據原鏈表中節點N的random指針指向的節點S,找到新鏈表中所對應的S',而不是簡單地將新鏈表中的N'的random指針指向S。
  5. 至于如何找到對應的S',有兩種方法:
    -要么都從頭指針開始遍歷經過指針N,N'且經過相同步分別找到S, S'。這樣的話,時間復雜度會到O(n^2)。
    -要么就是用空間換時間,使用Map存儲新舊鏈表中的對應的節點對<N, N'>
  6. 還有另外一種方法,既不用開辟新的空間來存儲節點,也不用從頭遍歷查找。方法比較巧妙:
>- 在第一步復制原始鏈表結點時,新結點鏈接在原結點后面,然后再將新結點的next指針指向原結點的next指針,具體如下:
鏈表.png
  • 第二步鏈接新結點的random指針就很容易了,它對應在原結點的random指針的后面一個結點,具體如下:
鏈表二.png

題目考點及相關題目

把復雜鏈表的復制過程分解成三個步驟:復制結點、設置random指針、拆分兩個鏈表。
相關題目:Clone Graph

我的代碼如下:

1.使用HashMap,空間換時間,時間復雜度$O(n)$,空間復雜度$O(n)$。

public class Solution {
    public RandomListNode copyRandomList(RandomListNode head) {
        if(head == null) return head;
        RandomListNode newHead = new RandomListNode(0);
        RandomListNode p = newHead;
        RandomListNode s = newHead;
        RandomListNode q = head;
        RandomListNode r = head;
        Map<RandomListNode, RandomListNode> nodes = new HashMap<RandomListNode, RandomListNode>();
        while(q != null){
            RandomListNode tmp = new RandomListNode(q.label);
            p.next = tmp;
            p = p.next;
            nodes.put(q, p);
            q = q.next;
        }
        //p.next = null;
        while(r != null){
          s.next.random = nodes.get(r.random);
          s = s.next;
          r = r.next;
        }
        return newHead.next;
    }
}

2.使用鏈接方式,方法巧妙,時間復雜度$O(n)$,空間復雜度$O(1)$。

public class Solution {
    public RandomListNode copyRandomList(RandomListNode head) {
        if(head == null) return null;
        RandomListNode p = head, q = p;
        while(p != null){
            RandomListNode tmp = new RandomListNode(p.label);
            RandomListNode next = p.next;
            p.next = tmp;
            tmp.next = next;
            p = next;
        }
        
        while(q != null){
            RandomListNode t = q.next;
            t.random = q.random == null ? null : q.random.next;
            q = t.next;
        }
        
        RandomListNode newHead = head.next, s = head;
        while(s != null){
            RandomListNode n = s.next;
            s.next = n.next;
            s = s.next;
            n.next = s == null ? null : s.next;
        }
        return newHead;
    }
}

面試題 27:二叉搜索樹與雙向鏈表

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

題目分析

  1. 注意題目要求:不能創建任何新的結點,只能調整指針的指向
  2. 因為在二叉樹中,每個結點都有兩個指向子結點的指針,在雙向鏈表中每個結點也有兩個指針,它們分別指向前、后兩個結點。因此,在理論上有可能實現二叉搜索樹和排序的雙向鏈表的轉換。
  3. 由于要求轉換之后雙向鏈表是排好序的,而二叉搜索樹的中序遍歷剛好是一個有序數組,因此此題的遍歷方法肯定是中序遍歷二叉搜索樹。
  4. 具體思想:如下圖,當遍歷到根節點10時,我們將樹看成三部分,根節點10、根節點為6的左子樹、根節點為14的右子樹,可以想像一下,如果左子樹已經排好序成為了雙向鏈表,那么它的最后一個節點(也就是左子樹里的最大值8)的右指針將指向10,同理,10的右指針將指向右子樹里的最小值12,如下圖所示:
  5. 很明顯,上述就是一個遞歸過程。


    中序.png

    中序2.png

題目考點及相關題目

分治法的本質:將復雜問題分解成小問題,然后遞歸調用函數功能,即可完成對復雜問題的求解。
相關題目:LeetCode 114. Flatten Binary Tree to Linked List

我的代碼如下:

1.未經驗證的Java代碼,時間復雜度較高,全耗在了遍歷左子樹,找到最后一個節點上面, $O(n^2)$。

public TreeNode Convert(TreeNode root){
   if(root == null) return null;
   TreeNode head = Convert(root.left);
   if(head == null) head = root;
   else{
       TreeNode p = head;
       while(p.right != null) p = p.right;
       p.right = root;
       root.left = p;
   }
   TreeNode right = Convert(root.right);
   root.right = right;
   if(right != null)
      right.left = root;
   return head;
}

2.標準答案見P170

面試題 28:字符串的排列

題目:輸入一個字符串,打印出該字符串中字符的所有排列。例如輸入字符a、b、c所能排列出來的所有字符串abc、acb、bac、bca、cabcba

題目分析

  1. 先不談解題思路,先看題目要求:打印出該字符串中字符的所有排列,如果字符串存在相同字符,那么打印出的結果也肯定會包含相同的字符串,但是只從題目字面上理解,是沒有讓我們去重的,因此可以不用管打印出的字符串中是否存在相同的情況;但是如果讓我們去重的話,先使用list.contains(o)判斷一下是否存在,再決定是否添加。
  2. 求解思路:
    -將一個字符串看成由兩部分組成:第一部分為它的第一個字符,第二部分是后面的所有字符;
    -我們求整個字符串的排列,可以看成兩步:首先求所有可能出現在第一個位置的字符,即把第一個字符和后面所有的字符交換;然后固定第一個字符,求后面所有字符的排列。
    -很明顯,這是典型的遞歸思路。

題目考點及相關題目

本質:按照一定要求擺放若干數字,可以先求出這些數字的所有排列,然后再一一判斷每個排列是否滿足題目所給定的要求。

本題擴展:求字符的所有組合(如對于字符串abc的所有組合a, b, c, ab, ac, bc, abc),求解思路仍然差不多,將輸入的n個字符看成兩部分:第一個字符和后面所有字符,在構成長度m的組合時,分兩種情況考慮:1)如果包含第一個字符,則需要從后面的所有字符中選取m-1個字符,2)如果不包含第一個字符,則從后面的所有字符中選取m個字符。分析到這里,就感覺可以用動態規劃了,遞推公式:$f(n,m) = f(n-1, m-1) + f(n-1, m)$,具體實現就不細說了。
相關題目:八皇后問題(回溯);8個數字放在正方體8個頂點上,問是否有可能使得正方體三組相對的面上的4個頂點的和都相等。

我的代碼如下:

public List<String> Permutation(String string){
    List<String> res = new ArrayList<String>();
    Permutation(new StringBuffer(string), 0, res);
    return res;
} 
public static void Permutation(StringBuffer sb, int start, List<String> list){
        if(start == sb.length()) {
            // 這是去重后的結果
/*          if(!list.contains(sb.toString())){
                list.add(sb.toString());
                System.out.println(sb);
            }*/
            // 不去重的結果
            list.add(sb.toString());
            
        }else{
            for(int i = start; i < sb.length(); i ++){
                char temp = sb.charAt(i);
                sb.setCharAt(i, sb.charAt(start));
                sb.setCharAt(start, temp);
                
                Permutation(sb, start + 1, list);
                
                // 對更改的內容進行還原
                temp = sb.charAt(i);
                sb.setCharAt(i, sb.charAt(start));
                sb.setCharAt(start, temp);
            }
        }
    }

第4節:本章小結

當遇到難題,沒有任何思路時,一般的求解辦法:畫圖、舉例、分解
具體算法就會涉及到分治法動態規劃法,都是通過分解問題而來。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 1.把二元查找樹轉變成排序的雙向鏈表 題目: 輸入一棵二元查找樹,將該二元查找樹轉換成一個排序的雙向鏈表。 要求不...
    曲終人散Li閱讀 3,341評論 0 19
  • 說明: 本文中出現的所有算法題皆來自牛客網-劍指Offer在線編程題,在此只是作為轉載和記錄,用于本人學習使用,不...
    秋意思寒閱讀 1,164評論 1 1
  • 劍指 offer 在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成...
    faremax閱讀 2,225評論 0 7
  • 第一章 緒論 什么是數據結構? 數據結構的定義:數據結構是相互之間存在一種或多種特定關系的數據元素的集合。 第二章...
    SeanCheney閱讀 5,796評論 0 19
  • 注意:本文適用于已刷過題目,想短短幾分鐘快速簡單回顧的情況。沒看過《劍指offer》的讀者建議先閱讀下。 斐波那契...
    FeelsChaotic閱讀 1,736評論 2 8