算法筆記


1、逆序想到 stack ; 比如 445. 兩數相加 II,當然,可以用 stack 也是可以用 List,list 有序,因此也是可以當作 stack 用;

2、要求達到 O(n \log n)O(nlogn) 的時間復雜度和 O(1)O(1) 的空間復雜度,時間復雜度是 O(n \log n)O(nlogn) 的排序算法包括歸並排序、堆排序和快速排序(快速排序的最差時間復雜度是 O(n^2)O(n2)),其中最適合鏈表的排序算法是歸並排序。

3、對於鏈表,有時候需要把頭結點加入循環,可以在頭結點前面在家一個節點,這樣就可以用於判斷了;

4、鏈表常用的操作邏輯,用 stack, 數組保存節點組成新的隊列;用一個新節點來連接原來的節點,這樣,即使對原有節點改變了,還是可以返回頭結點。熟悉鏈表的反轉,合並等邏輯。

5、關於二叉樹,二叉樹一般采用遞歸,一般是遞歸到最后一個的時候,在不斷往前,因此你要做好最好一個判斷到底是返回什么,比如深度的求解等等。

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        } else {
            int leftHeight = maxDepth(root.left);
            int rightHeight = maxDepth(root.right);
            return Math.max(leftHeight, rightHeight) + 1;  // 這里加1,不斷遞歸,數值就變大了
        }
    }
}

 6、二叉樹的遞歸調用其實就是存在一個隱藏的stack。只是大家平時熟悉了遞歸,沒有去細想內部的邏輯。所以如果不采用遞歸方式來實現,就是得采用 stack 來維護這個順序,但是要注意先入后出這個點。

7、如果給你一個數組 num1 足夠空間,前部分已經存在值了,讓你把另一個 num2 的值填到 num1 中。這種要么新建一個數組,填完后再挪到 num1 中,另一種是從合並后的長度開始填,這樣確保不會覆蓋 num1 前面已有的值。可參考 88. 合並兩個有序數組

8、兩個字符串找不同,對於字符串可以采用求和,位運算,以及一個數組來記錄各個字母出現的個數,以此來找到不同。參考:389. 找不同

 1 // 求和
 2 class Solution {
 3     public char findTheDifference(String s, String t) {
 4         int as = 0, at = 0;
 5         for (int i = 0; i < s.length(); ++i) {
 6             as += s.charAt(i);
 7         }
 8         for (int i = 0; i < t.length(); ++i) {
 9             at += t.charAt(i);
10         }
11         return (char) (at - as);
12     }
13 }
14 
15 // 計數
16 class Solution {
17     public char findTheDifference(String s, String t) {
18         int[] cnt = new int[26];
19         for (int i = 0; i < s.length(); ++i) {
20             char ch = s.charAt(i);
21             cnt[ch - 'a']++;
22         }
23         for (int i = 0; i < t.length(); ++i) {
24             char ch = t.charAt(i);
25             cnt[ch - 'a']--;
26             if (cnt[ch - 'a'] < 0) {
27                 return ch;
28             }
29         }
30         return ' ';
31     }
32 }
33 
34 // 位運算
35 class Solution {
36     public char findTheDifference(String s, String t) {
37         int ret = 0;
38         for (int i = 0; i < s.length(); ++i) {
39             ret ^= s.charAt(i);
40         }
41         for (int i = 0; i < t.length(); ++i) {
42             ret ^= t.charAt(i);
43         }
44         return (char) ret;
45     }
46 }
兩個字符串找不同

9、翻轉二叉樹,這道題目遇到過好幾遍,雖然知道翻轉,但是就不知道如何寫比較好:226. 翻轉二叉樹

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return null;
        }
        // 如果此時左右節點已經為 null 了,那么就會執行后面的交換邏輯,然后遞歸就會回到父節點那一層
        TreeNode left = invertTree(root.left);
        TreeNode right = invertTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}
    

 10、關於遞歸。我感覺自己對遞歸還是沒有理解透徹。

對於509. 斐波那契數,我可以很輕松寫下 如下代碼,並不會覺得有啥問題。

class Solution {
    public int fib(int n) {
        if (n==1 || n==2) return 1;
        return fib(n-1)+ fib(n-2);
    }
}

同樣的,對於129. 求根節點到葉節點數字之和,也是遞歸,但是對於二叉樹的遞歸我確實很忐忑,總是覺得自己寫的代碼會有啥問題,具體代碼如下:

class Solution {
    public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int prevSum) {
        if (root == null) {
            return 0;
        }
        int sum = prevSum * 10 + root.val;
        if (root.left == null && root.right == null) {
            return sum;
        } else {
            // 既然你知道是遞歸,同時你也把遞歸的分支搞清楚了,只有左右兩個分支,因此你就寫下就是了
            return dfs(root.left, sum) + dfs(root.right, sum);
        }
    }
}
    

 還有就是我在想,是不是應該按照第一題一樣,先把遞歸的狀態搞清楚,也就是當前狀態和前面幾個狀態是啥關系,有幾個分支。都理清楚了可能就好寫了。然后把遞歸的結束條件分析清楚就好,結束條件放在前面,遞歸邏輯放在后面,只有沒有結束才是可以進入遞歸的。

10、對於二分法要時刻關注只有兩個元素的情況,比如:[1,2],[2,1]。這時候 middle = left。這時候注意 left 和 right 之間的關系。

11、在做加法和乘法的點時候要考慮會不會存在溢出的情況,一旦溢出,結果就不一樣了。可以考慮乘法變成除法,加法變成減法,以此逃過溢出。

12、在做加法乘法的時候,int 很容易溢出,這時候可以考慮使用 long 類型,比如在用 while 做 10 的階乘的時候,很容易就突然超了 int 的值,一般循環個 9 -10 次就差不多了 。 400. 第 N 位數字

13、如何理解動態規划。有時候很多題目我們知道要用動態規划,可是總會想這么做到底對不對,會不會錯過什么的。其實就是要理解動態規划的無后向性。比如 1696. 跳躍游戲 VI,其實它就是只有 k 種選擇。如果我們將到某個位置 pos 的前 k 種情況都列舉出來了,那么到 pos 位置只能從 k 種里面去選最大或者最小的。在往前考慮沒有意義了,最多只有 k 步。同時對於后面的點位 last,last 位置前的 k 位其實都已經計算出來了,選出最優的結果就好。

記住每一個點都有自己的一個最優解。對於 pos 位置,就是前 k 個位置的最優解一起放出來進行比較,在選擇最優的。

        // 總共有nums 個元素
        for (int i = 1; i < nums.length; i++) {
                // 只有k個選擇
                for (int j = Math.max(0, i - k); j < i; j++) {
                        // 比較這 k 個的大小
                        dp[i] = Math.max(dp[i], dp[j]);
                }
                // 選擇最優的,然后再加上當前位置,就是當前 i 的最優解
                dp[i] += nums[i];
        }

// 這段代碼有個問題就是,每次 K 個元素的比較其實會有重復的,相當於是一個移動窗口,窗口大小不變,里面的元素相互比較。因此,對於優化,可以從這里考慮           

 14、關於動態規划,我覺得你還是得理解 dp 表示的含義,這個含義一般就是題目要求的結果,但是可能是解釋上還是有點區別的。然后接着去理解每一個[][]索引所代表的含義。然后再逐步優化。121. 買賣股票的最佳時機,參考解答:暴力解法、動態規划(Java)。還有比如這題 392. 判斷子序列 返回的是布爾值,那么你要如何去定義 dp 的含義呢?參考解答。這些都是值得學習的地方。

 

附算法系列文章

滑動窗口算法基本原理與實踐

廣度優先搜索原理與實踐

深度優先搜索原理與實踐

雙指針算法基本原理和實踐

分治算法基本原理和實踐

動態規划算法原理與實踐

算法筆記


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM