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 的含義呢?參考解答。這些都是值得學習的地方。
