LeetCode算法總結——鏈表、樹、數組列表、整數和字符串等

前言:
做算法題,切記不要眼高手低。爲什麼明明感覺很容易的題目,在寫代碼實現的時候就思路混亂呢?細節!實現的細節是繁雜而且不容易記憶的。有什麼好的辦法嗎?模塊化!細節模塊化。譬如很多問題的解決方法,在首先引入排序後思路就明朗了,問題也就迎刃而解。這就是問題的泛化和規約。排序是個通用的模塊,它的細節、特性和適用的範圍是可以通過多次練習記憶的。除了代碼塊外,數據結構本身也是經常用到的模塊,如LinekedList, HashMap, Array等。在這個意義上,模塊裏面的實現就是具體的策略,這裏稱“打法”。掌握了這些模塊的打法,那我就可以上戰場了吧?呸!尼古拉斯·趙四!那麼對於一道題目,我們還需要弄明白什麼才能不至“看題一時爽,手寫火葬場”的悲劇呢?通用模塊與題目的耦合關係。這種耦合關係就是一種“戰術”,指導着模塊與模塊,模塊與膠水代碼的交互。直接完整搬運整個模塊可能需要較多的膠水代碼輔助,不過思路是清晰的。如果膠水代碼過於難看,則需要考慮模塊的變通性了,巧用模塊,融入到整體代碼中,渾然天成爲高境界。有了“戰術”和“打法”,我們就能高效地解決這些問題了嗎?當然不能!還缺少了最重要的一個東西,“戰略”!“戰略”是“兵來將擋水來土掩”的選擇,它是抽象的,是問題的應對策略。有些題適合用迭代,有些則適合用遞歸,有些用動態規劃,有些用回溯…看,這些就是戰略。
親愛的朋友們,祝你們能夠開心,Goo0o0ooD Luck。

========== LinkedList 鏈表 ==========

翻轉鏈表

[206 Reverse Linked List]

★ ❥ 📰

  • 戰略:迭代。戰術:輔助previous指針或頭插法,while(head != null)鏈表遍歷,
  • 遞歸:head == null || head.next == null邊界條件保證遞歸結束在最後一個非空節點。先遞歸到頭,在返程修改指針(兩次)。

[92 Reverse Linked List II]

★★ 📰

Example:
Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL
  • 模塊化:截取片斷鏈表反轉再和原來的連接起來。❥先走幾步模塊和❥反轉模塊。
  • 注意:先走幾步模塊需要走到被截取節點前一個,因此需要使用❥輔助頭節點法。

[25 Reverse Nodes in k-Group]

★★★ 📰

  • 模塊化:先走幾步模塊和反轉模塊。遞歸。翻轉頭幾個,連接到餘下的自身遞歸的結果。這是一種常見的一維數據遞歸的模板。同樣在樹的遞歸構造中,也和此有異曲同工之妙。

鏈表排序

[21. Merge Two Sorted Lists]

★ ❥ 📰

  • 輔助頭節點法。從輔助節點開始,貫穿一條鏈,串起兩條鏈表。

[148. Sort List]

★★ ❥ 📰

  • 歸併排序,模塊化。參考21的歸併函數。❥快慢雙指針半分鏈表法。注意:半分鏈表時需要將慢指針next的屬性置空切分開鏈表。
  • 快排,❥交換算法交換節點的值。❥partition函數雙指針同向,partition函數需要tail尾節點參數。注意的一點是鏈表的慢指針在結束時的位置的值應小於參考值,這樣再交換參考節點(第一個)和慢指針節點的值時就方便了。non-stable。(不像數組,索引可以方便的-1+1等操作,怎麼搞都好交換)。In-Place.
  • 插入排序:模塊化,截取節點(剩餘的銜接到前面),然後從前往後比較值插入。輔助頭結點法。

鏈表元素重排

[86. Partition List]

★★ ❥ 📰

  • 輔助頭節點法,兩個輔助頭節點。分拆成小於和大於等於兩個鏈表,然後再合併,注意合併時膠水代碼;

[328. Odd Even Linked List]

★★ ❥ 📰

  • 注意這裏奇數偶數指的是節點的順序,奇數順序的節點在前,偶數在後。❥鏈表的間隔k抽取法,odd.next = odd.next.next; 。如果只要單獨的兩個鏈表,記得鏈表末尾指針置空。

[143. Reorder List]

★★ ❥ 📰

  • L0→Ln→L1→Ln-1→L2→Ln-2→…
  • ❥快慢雙指針半分鏈表。fast=head.next, slow=head, while(fast!=null&&fast.next!=null)。❥合併鏈表模塊,以較短的爲基準(後半部分),這裏一步要操作兩個方向。

移除鏈表中某節點

[83. Remove Duplicates from Sorted List]

📰

  • 刪除第二個重複節點(跳過,指針置爲空)

[82. Remove Duplicates from Sorted List II]

★★ ❥ 📰

  • 輔助頭節點法(aux)。前後兩指針,prev, cur, cur負責判斷cur與cur.next,prev負責鏈接最終的結果,注意這裏有個內嵌的while小循環。
  • 需要注意的一點是,prev從aux開始,cur則從head開始,避免了頭兩個節點的無謂比較。

[19. Remove Nth Node From End of List]

★★ 📰

  • 迭代,先走幾步模塊,❥前後雙指針協同走模塊(一直都是每步一個節點,和快慢雙指針不一樣),輔助頭節點法。快節點的終止條件while(fast.next != null)。

[203. Remove Linked List Elements]

📰

  • 迭代,輔助頭節點法+提前量。

鏈表與環,Math

[2. Add Two Numbers]

★★ ❥ 📰

  • ❥ 兩鏈表相加,比兩數組相加更容易,注意當最後進位是1時,要新建節點。

[445. Add Two Numbers II]

★★ 📰

  • 模塊化,反轉相加再反轉。

[61. Rotate List]

★★ 📰

  • 迭代。左旋x:從前開始第x節點向後切斷;右旋x:從後往前第x節點處向前切斷(類似Find And Delete Nth From end)或等於左旋n-k。關鍵操作點:連接首尾成環狀,後面使用先走幾步模塊。❥求鏈表長度模塊(1.同先走幾步模塊幾乎一樣,2. cnt=0; while(head!=null){head = head.next}。

[141. Linked List Cycle]

📰

  • 快慢雙指針ListNode fast = head.next, slow = head; while (fast != null && fast.next != null)…

[142. Linked List Cycle II]

★★ 📰

  • 先快慢指針走到相遇,再用個其他慢指針2從頭開始,兩滿指針走到相遇即可。

[160. Intersection of Two Linked Lists]

📰

  • // 思路:雙指針,一個走鏈表1, 一個走鏈表2,各自走到頭則從另一個鏈表頭開始,直到會和。匯合點即第一個公共點。 雙指針走了相同的距離。

Linked List And Tree

[109. Convert Sorted List to Binary Search Tree]

★★ 📰

  • 遞歸,模塊化。❥有序鏈表轉List, List轉BST(遞歸).TreeNode n = new TreeNode(list.get(mid)); n.left = formBST(left, mid-1); n.right = formBST(mid+1, right);

其他

[138. Copy List with Random Pointer]

★★ 📰

  • 迭代。❥在每個節點後插入複製節點, 循環條件while(cur != null)。奇數偶數順序鏈表拆分。

算法模擬:
玩遊戲

========== Tree 樹 ==========

樹的遍歷

[144. Binary Tree Preorder Traversal]

★★ ❥ 📰

  • 遞歸:輔助遞歸函數void helper(TreeNode root, List list)
  • 迭代:棧。LinkedList 模擬棧。先進個根結點。循環條件while(!stack.isEmpty())。進棧時先右節點後左節點。

[94. Binary Tree Inorder Traversal]

★★ ❥ 📰

  • 遞歸:輔助遞歸函數void helper(TreeNode root, List list)
  • 迭代:棧。LinkedList 模擬棧。循環條件TreeNode cur = root; while(!stack.isEmpty() || cur != null)。// 中序的情況比較奇怪,不需要先入根結點。因爲中間過程中,根節點彈出後棧就爲空了,致使循環結束,右子樹無法遍歷。因此還需要另外一個可以使循環繼續的條件:cur 節點不斷的變化。循環體中先一通向左dfs到最左節點,其間順帶入棧。彈出節點加入鏈表,再從彈出節點右節點繼續。

[145. Binary Tree Postorder Traversal]

★★★ 📰

  • 遞歸:輔助遞歸函數void helper(TreeNode root, List list)
  • 迭代:棧。LinkedList 模擬棧。先進個根結點。循環條件while(!stack.isEmpty())。定義輔助指針TreeNode prev = null;先左子樹各個節點入棧一波再出棧完,每次需要通過用peek來獲取頭部,進而加入樹的下層節點(先右後左),再右子樹入棧一波再出棧完,最後出棧根節點。
    入棧條件:((cur.right != null || cur.left != null) && ((prev == null) || (cur.right != prev && cur.left != prev)))
    1. 入棧過程必要條件一: (cur.right != null || cur.left != null) cur當前節點還沒有到底的過程
    2. 入棧過程必要條件二: ((prev == null) || (cur.right != prev && cur.left != prev)) 要麼prev == null,意思是左子樹各個節點入棧一波的過程,這個時候prev還是乾淨的; 要麼cur.right != prev && cur.left != prev 意思是除了上一個過程外的所有入棧過程
      出棧條件:入棧條件的相反。出棧時只需簡單地stack.pop(); prev = cur; 即可。

[102. Binary Tree Level Order Traversal]

★★ ❥ 📰

  • 迭代。隊列。LinkedList 模擬隊列。先進個根結點。循環體中記錄當前一層的size,內循環逐個poll出和加入新的下層節點(先左後右),其間將元素記錄到臨時List中。

[103. Binary Tree Zigzag Level Order Traversal]

★★ ❥ 📰

  • 同102. 循環外設置判斷層的奇數偶數的變量,加入每次節點的值時判斷是否需要Collections.reverse(層節點值臨時List)。

[107. Binary Tree Level Order Traversal II]

★★ ❥ 📰

  • 迭代。同102, 結果使用Collection.reverse翻轉即可。

[662. Maximum Width of Binary Tree]

★★ 📰

  • 迭代。層次遍歷,放入null空值。設置變量maxWidth存最大寬度即可。注意:1. 當一層中所有的節點都是null的時候,這一層其實已經到了“-1”層了,不能計入最大的寬度。2. 當每層的第一個或最後一個節點是null的時候,該null節點不計算其下面的兩個null。

BST

[109. Convert Sorted List to Binary Search Tree]

★★ 📰

  • 遞歸,模塊化。❥有序鏈表轉List, List轉BST(遞歸).

[108. Convert Sorted Array to Binary Search Tree]

★ ❥ 📰

  • 模塊化。有序數組轉BST(遞歸),同109.

[98. Validate Binary Search Tree]

★★ 📰

  • 模塊化+檢查中序遍歷的有序性
  • 不必額外浪費O(n)空間存儲中序遍歷的整個結果,將中序遍歷與檢查結合起來
    • 遞歸的中序遍歷。遞歸本身的性質,致使不能夠在遞歸函數體中Fast fail/success,因此需要額外的isValid的變量存儲flag。Integer inOrderMax = null; 其間first 有可能是Integer.MIN_VALUE,這是允許的情況, 此時對inOrderMax進行賦有意義初值,所以循環過程需要對inOrderMax進行非空判斷。

[235. Lowest Common Ancestor of a Binary Search Tree]

★ ❥ 📰 【未完】

  • 遞歸。模塊化。1.同236二叉樹的LCA。 2.根據root.val p.val q.val將問題範圍縮小。

[199. Binary Tree Right Side View

★★ ❥ 📰

  • 遞歸。右視圖使用"變形的前序遍歷"(先根節點,後右節點再左節點),dfs helper遞歸函數傳depth深度,每層加1,維護全局最大深度變量,僅有當且僅噹噹前深度大於記錄的最大深度的第一個節點,添加其值到結果列表即可。
  • 迭代。隊列,每層記錄size()到局部變量size, 遞減循環size直到size爲1時加入值即可。

樹的深度相關

[111. Minimum Depth of Binary Tree]

★ ❥ 📰

  • 遞歸。排除掉子樹的高度爲0時候的情況。左右子樹深度分別是left, right, 則min = left == 0 ? right+1 : right == 0 ? left+1 : (int)Math.min(left, right)+1;。

[104. Maximum Depth of Binary Tree]

★ ❥ 📰

  • 遞歸。I.1 + Math.max(maxDepth(root.left), maxDepth(root.right))。II.模塊化結合樹的postOrder DFS, DFS遞歸過程中層層傳遞當前depth參數,每層加一,求左右過程的最大值即可。return Math.max(dfs(root.left, depth), dfs(root.right, depth))
  • 迭代。隊列層次遍歷,每次根據隊列的size()出隊時記錄計數器。

[559. Maximum Depth of N-ary Tree]

📰

  • 遞歸,注意foreach遍歷每個子樹,輔助變量求最大子樹深度。

[543. Diameter of Binary Tree]

📰

  • 同111. 在求解深度的函數中,對直徑變量進行計算(遞歸過程中不容易返回、傳遞值的,全局變量!左右子樹的深度分別是l,r, 則diameter = Math.max(diameter, l+r);

[110. Balanced Binary Tree]

📰

  • 雙層遞歸。模塊化。首先從根節點開始,判斷左右子樹的高度差,大於1直接返回。然後遞歸左右子樹各個節點。

樹的操作

[101. Symmetric Tree]

📰

  • 遞歸。遞歸 helper(TreeNode left, TreeNode right)同時接受兩個節點,分類討論,然後return helper(left.left, right.right) && helper(left.right, right.left);
  • 迭代。BFS隊列實現,核心還是左右節點及其葉子節點的加入順序。先仍進入兩節點,在循環體中每次吐出兩個,分別爲left,right, 分類討論,再加入left.left, right.right, left.right, right.left

[226. Invert Binary Tree]

★ ❥ 📰

  • 遞歸。1. 先交換根節點下的兩個左右子節點,再分別遞歸左右子樹。2. 先遞歸到底,返程過程交換左右子節點。
  • 迭代。層次遍歷bfs。隊列poll出來的節點進行左右子節點交換,後續入隊操作和正常一樣。

LCA (Lowest Common Ancestor)

[236. Lowest Common Ancestor of a Binary Tree]

★★ 📰

  • 遞歸。終止條件root == null || root == p || root == q 。左子樹找兩節點,右子樹找兩節點,分別判斷是否爲空,得出結果。

[1123. Lowest Common Ancestor of Deepest Leaves]

★★ ❥ 📰

  • 模塊化。參考求樹深度的postOrder方法,只在最深深度時記錄當前的root爲target,遞歸完後,最終的target即爲所求。

最值

[1026. Maximum Difference Between Node and Ancestor]

★★ 📰

  • 遞歸。❥從根節點開始計算根節點與所有子樹節點的最大差值。遞歸左右子樹。這樣所有的節點都當過一邊祖先節點。

[654. Maximum Binary Tree]

★ ❥ 📰

  • 遞歸。生成規則:最大值爲根節點,最大值左側在左子樹,右側右子樹,如此遞歸。最大樹的中序遍歷即是給的原數組。

[998. Maximum Binary Tree II]

★★ 📰 【未完】

  • 模塊化。已知一個最大樹和一個不重複的節點,生成一個最大樹。最大樹先中序遍歷得到數組,插入新的節點到最後,再生成最大樹。

樹中PATH及其SUM

[113. Path Sum II]

★★ ❥ 📰

  • 遞歸。路徑(根-葉). 返回滿足條件所有路徑List<List>。滿足的條件root.left == null && root.right == null && root.val == sum。遞歸的函數參數傳遞路徑,只用一個path列表,通過在每一層遞歸的最後remove掉該層添加的元素,保持path不被之前的操作污染。 path.remove(path.size()-1);

[113. Path Sum]

📰

  • 遞歸。遞歸函數不好Fast Success, 設置類變量存儲。

[113. Path Sum III]

📰

  • 雙層遞歸。局部路徑(任意節點間)的和爲sum值的全局變量加一。滿足條件root.val == sum。

生成樹

[105. Construct Binary Tree from Preorder and Inorder Traversal]

★★ ❥ 📰

  • 遞歸。1. 從前序數組得知的root,將中序遍歷數組切分 2. 構建TreeNode, 左右遞歸得到左右節點。注意:左子樹下前序的1…rootIndex+1,和中序的0…rootIndex個是可以對上的, 右子樹前序rootIndex+1…end 對應中序rootIndex+1…end。
  • 如果只是返回一個後序列表, 可以不用重建二叉樹。 設置類變量: private ArrayList list = new ArrayList<>(); 遞歸函數無需返回值,只需在末尾 list.add(root.val); 即可

[105. Construct Binary Tree from Preorder and Inorder Traversal]

★★ ❥ 📰

  • 遞歸。類似105. 左子樹中序0…rootIndex對應後序0…rootIndex,右子樹中序rootIndex+1…end對應後序rootIndex…end-1。

樹與其他結構的轉換

  • tree->list (樹的遍歷)
  • tree->linkedlist: 1. tree->list->linkedlist, 2. tree->linkedlist

[114. Flatten Binary Tree to Linked List]

★ ❥ 📰

  • 遞歸。後序遍歷+最後一步的操作是把左子樹接到右子樹上。注意需要對原左子樹銜接上舊的右子樹。是因爲後序到最後一步操作的時候,左右兩個子節點都已經知道了,便於操作。
  • 迭代。把左子樹移到右子樹,一直深入右遍鏈到底,銜接上舊的右子樹。如此循環此操作,每次一個root.right步驟。while(temp.right != null) { temp = temp.right; }

========== List|Array 數組列表 ==========

基本操作

https://blog.csdn.net/weixin_41615787/article/details/85115620

  • 數組轉列表

    • for…
    • Arrays.asList(arr) 返回基於arr的視圖,長度固定,只能set,不能add/delete, 但是速度快。不考慮性能的情況下,建議new ArrayList(Arrays.asList(arr))。
    • Collections.addAll(new ArrayList(arr.lenght), arr)
  • 數組轉列表 (primitive type)

    • for…
    • = JDK1.8, int[] -> List: Arrays.stream(arr).boxed().collect(Collectors.toList());

  • 列表轉數組

    • for…
    • List -> Integer[]: list.toArray(new Integer[list.size]) ok, (Integer[])list.toArray() 是錯誤的,toArray()返回的是Object對象,自然不能夠由高到低cast.
  • 列表轉數組(primitive type)

    • for…
    • = JDK1.8, List -> int[], list1.stream().mapToInt(Integer::valueOf).toArray();

  • 數組基本類型和包裝類型轉換

    • Arrays.stream(intergerArr).mapToInt(Integer::valueOf).toArray();
    • Arrays.stream(arr).boxed().toArray(Integer[]::new);

數組排序

[912. Sort an Array]

★ ❥ 📰

refer: https://blog.csdn.net/fzlulee/article/details/102573566

  • 快排。t O(nlgn), s O(lgn) 遞歸的棧空間,輔助變量
  • 歸併。t O(nlgn), s O(n)
  • 插入。t O(n^2), s O(1)
  • 選擇。t O(n^2), s O(1)
  • 堆排序。t O(nlgn), s O(lgn)

TOPK, Kth Largest

[215. Kth Largest Element in an Array]

★ ❥ 📰

  • ❥快速選擇算法。時間O(lgn). Kth Largest即爲(N-K)th Smallest。利用每次partition的軸心點分割問題,達到類似二分的效果。
  • 快排然後索引。時間O(nlgn).
  • 另外,參考求中位數的雙堆法(大頂堆/小頂堆,始終保證兩個條件:1.大頂堆的最大不大於小頂堆的最小 2. 二堆大小之差不大於1),該方法的一個缺點時要把所有的數都存起來,空間O(n)

[703. Kth Largest Element in a Stream]

★ ❥ 📰

  • 小根優先隊列。對於流,用現成的數據結構比較方便。構造函數初始化,每次加入新的元素進行頭部比較。

N Sum

[Two Sum]

★ ❥ 📰

  • hashmap,一邊解決問題。t O(n), s O(n)
  • 排序+雙指針夾逼。t O(nlgn), s O(1)。注意在原int[]數組副本上進行排序(可用對象的Clone),因爲題目要求返回的原數組元素的索引。

[3 Sum]

★★ 📰

  • 排序後逐個遍歷,先固定一個,在後續的數組中雙指針查找。注意跳過重複的元素和初始時的優化: 如果當前的元素和前一個相同,那麼直接跳過。如果後續數組中左右雙指針也要考慮是否會加入和已得到的答案重複的。

關於重複

[136. Single Number]

📰

  • hashmap. Map.Entry<Integer, Integer> entry : map.entrySet()
  • xor. 只有一個單數,其他數都是雙的,雙的異或掉了,剩下單數。

[268. Missing Number]

📰

  • 數組長度n, 元素取值範圍0 ≤ a[i] ≤ n, 0…n的總和逐個減去出現的元素,最終剩下的即使missing number。
  • xor. 0…n所有的異或再異或所有出現的元素,最後即所求。異或的交換律。一個數異或自身等於0. 任何數異或0仍爲自身。
  • 排序。

[287. Find the Duplicate Number]

📰

  • 要求不能修改數組。O(1) 空間。
  • 參考有環鏈表用快慢指針找鏈表的環入口的方法。O(n), O(1)。巧妙!使用類似環形鏈表找環入口的解法的前提:n+1個數,每個數的大小1~n之間(這樣確保按照元素值大小去遍歷時不越界),僅一個重複值。在如此前提下,構成鏈式結構:nums[i] -> nums[nums[i]] -> … -> prev -> nums[prev], 起點爲數組第一個元素。如果不同位置存在重複的數,那麼他們各自指向的下一個數必然是相同的,即成了環。尋找重複整數即轉化爲找鏈表的環入口。

[442. Find All Duplicates in an Array]

★ ❥ 📰

  • 元素位置調整:從前往後,逐個檢查元素是否各就位或是鳩佔鵲巢(原巢已被佔領)。數組中所有重複的元素找出來:元素各就位調整後,找出元素與索引不匹配的元素即可。
  • Note: 1) 1 ≤ a[i] ≤ n, 調整元素位置時需要-1的動作。2) 0 ≤ a[i] ≤ n-1, 調整元素位置時直接用。

[448. Find All Numbers Disappeared in an Array]

★ ❥ 📰(https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/)

  • 同442,只在最後遍歷重排的數組時有區別:元素各就位調整後,找出元素與索引不匹配的索引即可。

最值,滑動窗口

[581. Shortest Unsorted Continuous Subarray]

📰

  • 4個循環。從前到後,找出趨勢下跌後最小的,記爲min;從後往前,找出趨勢上升後最大的, 記爲max。從前往後,找出第一個大於min的位置i, 從後往前,找出第一個小於max的位置j, i-j即爲所需數組。
  • 2個循環。從後往前,同時記錄最小值,和大於最小值的元素,到頭即可得到最小需排序窗口的左邊界。同理右邊。
  • 注意,這種數組比較類的最值初始一般選Integer.MIN_VALUE,MAX_VALUE.

[121. Best Time to Buy and Sell Stock]

★ ❥ 📰

  • dp. 動態規劃,一遍循環。兩個變量dp和curMin. dp[i]代表截止到索引i的最大利益,比較當前值和之前的最小值得差值,當前的最大利益取此差值和之前最大利益的大者。dp[i] = max(dp[i-1], cur - curMin). 優化後dp = max (dp, cur - curMin)。

[53. Maximum Subarray]

★ ❥ 📰

  • dp. 動態規劃,兩個變量dp和maxSubArray,dp[i]存儲截至到索引i的maxSubArray,。 轉移方程爲一階. dp[i] = dp[i - 1] > 0 ? dp[i - 1] + nums[i] : nums[i]); 由於不需要dp的中間狀態,可以將dp空間優化到1。dp = dp > 0 ? dp + nums[i] : nums[i]

[152. Maximum Product Subarray]

📰

  • dp. 動態規劃。 注意要正正的正,負負得正的情況,其實需要三個dp變量,dp, curMax, curMin。注意因爲題意需要的連續性,這個curMax, curMin不是累計最值,而是當前的最值,因此這裏dp纔是累計最值。curMax = nums[i] > 0 ? Math.max(nums[i], nums[i] * curMax) : Math.max(nums[i], nums[i] * curMin); 同理對curMin, 當心需要對curMax作暫存,以免污染。dp = Math.max(dp, max);

[239. Sliding Window Maximum]

★★★ ❥ 📰

  • 暴力。t O(nk), s O(1). 結果數組的長度n-k+1.
  • 雙端隊列 Deque dq = new LinkedList<>(); 存放索引值。 t O(n), s O(1). 單調遞減存放的過程是從後往前,隊列中小於等於當前值得都出隊pollLast();每次循環,都檢查隊列頭部(peekFirst())的索引是否出了範圍i-k。
  • 類似利用隊列做滑動窗口的題目:3. Longest Substring Without Repeating Characters

[150. Evaluate Reverse Polish Notation]

★★ ❥ 📰

  • 逆波蘭表達式求解,也叫後綴表達式(將運算符寫在操作數之後)可以不用括號進行數學計算。
  • 棧。涉及到的運算符都是雙目的,運算符都在操作數後面。因此,通過一個LinkedList的棧,存操作數,每次遇到運算符將棧中的兩個操作數彈出,運算後結果放入。最終棧中僅剩下結果值。

編碼

[54. Spiral Matrix]

★★ ❥ 📰

  • 編碼基本能力。四個邊界指針和一個剩餘數的變量,不斷循環直到剩餘數變量爲0退出。注意行列的最大最小值的動態變化。

[74. Search a 2D Matrix]

★★ ❥ 📰

  • 二分,二維轉一維。矩陣特點是每一行遞增,下一行接着上一行遞增。2d -> 1d, index k in 1d is matrix[k/matrix[0].length][k%matrix[0].length]。

[240. Search a 2D Matrix II]

★★ ❥ 📰

  • 二維二分。矩陣特點是每一行遞增,下一列從上到下遞增。從左下角或右上角開始,注意二分的循環條件。

距離

[hamming-distance]

★ ❥ 📰

  • 位運算,異或得到不同的位上值爲1,其他爲0. // 統計1的個數模塊。

[315. Count of Smaller Numbers After Self]

★★★ ❥ 📰

  • 暴力.
  • 插入排序法。❥參考插入排序統計數組逆序對。
  • 歸併排序法。❥參考歸併排序統計數組逆序對。注意需要使用索引數組,因爲這裏不是僅僅需要一個總的逆序對數,而是每個數字對應的右邊小於該數字本身的數個數,歸併排序會把順序弄亂。

[72. Edit Distance]

★★★ ❥ 📰

  • dp. dp[i][i]表示字符串1的第i個字符前的字符和字符串2第j個字符前的字串的編輯距離,注意這裏dp索引爲0時,表示的第0個,是空,但佔一個位置, 這是動態規劃常見的操作。需要先初始化頭一行和頭一列,一個字符串同一個“空字符串”的編輯距離就是字符串的長度。轉移方程dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1]) + 1) | Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1;

DP

[1143. Longest Common Subsequence]

★★ ❥ 📰

  • dp. 同72編輯距離的解法非常相似。dp[i][j]表示text1的第i個字符結尾的字串與text2的第j個字符結尾的子串的最長公共子序列的長度. dp[0][0] == 0, 第0個表示空字符串,dp[0][…] == 0, dp[…][0] == 0。轉移方程dp[i][j] = dp[i-1][j-1] +1 | dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
  • 注意:
  • 若果是最長公共字串(Substring),解法基本一樣,轉移方程dp[i][j] = dp[i-1][j-1] +1 | 0.

[300. Longest Increasing Subsequence]

★★ ❥ 📰

  • 一維dp. dp[i]表示以索引i對應的字符做結尾的最長遞增子序列的長度。注意LIS的末尾是索引i對應的字符,而不是索引i結尾子串對應的LIS的長度。(若爲後者情況,這狀態轉換方程複雜,涉及變量不好梳理)。最終的結果,需要遍歷整個dp數組元素中的最大值。
  • 以索引i爲起點,往前逐個推演,計算遞增的最大窗口大小。不同於LCS 兩個字符串用二維dp。

[70. Climbing Stairs]

★ ❥ 📰

  • dp. f[n] = f[n-1] + f[n-2]
  • 擴展:❥ 當樓梯數和最大步數都是傳入參數變量時:1. 遞歸,分剩下的樓梯大於最大跳躍數與否兩種遞歸公式

bag problem

Divide And Conquer

[218. The Skyline Problem]

★★★ 📰

  • 天際線勾勒算法。1. 借用歸併的思路:首先邊界情況考慮0個建築物直接返回空鏈表,遞歸的終止條件是一個建築物,返回建築物的左上角和右下角;二分輸入,調用歸併函數傳入左右二分輸入的遞歸值;歸併函數中,輔助的結果長度不一定是左右之和(因爲可能存在連續的高度相對的重複情況,需要剔除),while循環直到全窮盡。輔助變量,左右輸入當前的序號對應的x座標和y座標,每次取小的x座標和大的y座標。

Back-Tracing

[46. Permutations]

★★ ❥ 📰

  • 回溯法(backtracking)進行求解,它是一種暴力搜索方法,通過搜索所有可能的結果來求解問題。在隱式的決策樹解空間, 遞歸操作進行DFS, 在搜索的每一步都進行路徑選擇、遞歸和路徑選擇撤銷,以此遍歷到所有可能的結果。
    // psudo code:
    result = []
    def backtracking(path, optionalSelection):
    	// 不一定會有顯式的的終止條件;收集解的過程可能會放到了每次搜索步驟中;
        if terminationCondition:
            result.add(path)
        for selection in optionalSelection:
            // do selection
            pathAdd(selection)
            backtracking(newPath, newOptionalSelection)
            pathCancelAdd(selection)
  • backtracking(int[] nums, LinkedList path, List<List> ret) 遞歸,path即路徑,通過nums, path可推導出可選擇決策列表,ret記錄最終結果。

[77. Combinations]

★★ ❥ 📰

  • 同46回溯模板。注意,遞歸函數void backtracking(int n, int k, int start, LinkedList path, List<List> ret),可選擇決策列表由n, k, start共同推導出(這裏Cn^k, n限定了每次決策時寬度的上限,k是結束條件的深度,每次的可選擇決策範圍[start, k], 爲消除重複,for迭代可選擇決策列表時,start是逐次遞增的)

[78. Subsets]

★★ ❥ 📰

  • 類似77. backtracking(int[] nums, int start, ArrayList path, List<List> ret)。注意:首先加入空的子集;DFS每一個步驟都需要加入存在的解;需要輔助變量start記錄選擇的起始位置,可選擇決策範圍[start, max]。沒有顯式的終止條件,nums循環結束即自然終止,由於在每一步都會遇到可能解,因此將收集解的過程放到了每次搜索步驟中

[62. Unique Paths]

★★ ❥ 📰

  • 機器人走棋盤路徑。
  1. backtracking. bt函數參數中包含當前的座標位置,已經走過的座標(用二維數組中走過的位置置爲1即可),通過這兩個可以推導出可下一步的決策空間;每次決策最多兩種可能(下OR右,邊界時只一個)。
  2. dp。假想:將start 和 finish 顛倒過來,從finish 到 start的路徑總數等於start 到 finish的路徑數。dp[i][j] 表示座標(i, j)到(0,0)的路徑總數,dp[0][0] = 1.

[64. Minimum Path Sum]

★★ ❥ 📰

  • 從網格的左上角到右下角的最小路徑(各網格點帶權重)。
  • 動態規劃。dp[i][j]表示從左上角到(i,j)的最小路徑,dp[0][0]=grid[0][0]; dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]; 注意首行和首列初始化時,類似:dp[i][0] = dp[i-1][0] + grid[i][0];

Greedy

[122. Best Time to Buy and Sell Stock II]

★ ❥ 📰

  • 貪心。每一步只要後一天的價格高於今天的(只看到相鄰兩天的),就繼續持股,否則賣掉。這當然是事後的角度,而不是當時交易的角度來看的。

Design

[146. LRU Cache]

★ ❥ [📰](https://leetcode-cn.com/problems/lru-cache/)

  • 類變量 hashmap + double ended linkedlist, 其中linkedlist增加輔助的頭尾節點,方便節點刪除和添加的過程。 hashmap實現了O(1)的get,雙向鏈表實現O(1)的put。另外還需要capacity和size的類變量記錄狀態。
  • 新增、更新節點值或淘汰的過程都需要的的輔助步驟:removeNode(n Node), addNodeToFirst(n Node). get的過程是首先使用嘗試使用map獲取,獲取到更新節點到首位;put的邏輯是首先map檢查key的存在性,存在直接更新舊值,否則新建節點,插入鏈表首位,map中新設該key, value爲該node。

[225. Implement Stack using Queues]

★ ❥ 📰

  • 單個Deque實現棧
  • 兩個基本的Queue實現棧(始終保持至少一個爲空做倒騰):push時,檢查哪個不爲空push(初始隨便)。pop時,從不爲空的隊列轉移q.size() - 1個到另一個,剩下的一個pop即可。top基本同pop。

priorityQueue (小根大根)
Queue/ Stack 的基於隊列或鏈表的實現

[Immutable Stack]

========== String 字符串 ==========

## [3. Longest Substring Without Repeating Characters]
★★ ❥ 📰

  • Sliding Window. 1. hashmap 存放字符與其索引,在其中維護一個滑動窗口[i,j], 每加入一個元素前就檢查hashmap中是否存在相同值的元素(map.containsKey()),如若存在則考慮是否需要移動窗口左邊界i。每次循環記錄當前窗口的最大值,更新全局最大即可。2. Deque,隊列即是滑動窗口,每次加入元素需要判斷隊列中是否存在相同值(queue.contains()),如若存在,則從隊列頭部不斷pollFirst()直到將重複值移出,每次循環記錄當前隊列的size()即可。

[14. Longest Common Prefix]

★ ❥ 📰

  • 迭代。❥從前往後,逐個兩兩比較得到公共前綴,更新中間變量,到末尾即可。

[344. Reverse String]

★ ❥ 📰

  • 雙指針,輸入的是char[], 原地交換首位字符即可。如果輸入的是字符串,可以考慮使用遞歸, reverse(s.substring(1)) + s.charAt(0);。

[541. Reverse String II]

📰

  • 遞歸。2k個一組,依次翻轉前k個,k-2k不變。不足k或2k的亦是。類似於翻轉K-Group linkedlist.

[557. Reverse Words in a String III]

★ ❥ 📰

  • String 基本操作。split,stringBuilder, trim, reverse.

[28. Implement strStr()]

★ ❥ 📰

  • KMP暴力雙指針。t O(MN), 一個while循環,顯式地回退主循環指針。
  • KMP。

[392. Is Subsequence]

📰

  • 迭代。字符串的indexOf()方法。
  • 貪心。

[65. Valid Number]

★★★ 📰

  • 正則s.matches("[±]?\d*(\.\d+)?([eE][±]?\d+)?");

[415. Add Strings]

📰

  • 模塊化。首先翻轉操作數字符串。StringBuilder構建。一位變量同時表徵同位和以及進位。注意:最後進位若爲1,則需要補充上進位。
  • 擴展:36進制字符串相加。

[43 Multiply Strings]

★★ 📰

  • 模塊化。首先翻轉操作數字符串。結果字符串使用int[]暫存,長度爲兩字符串長度之和。StringBuilder構建最終的結果。
  • 注意:1.兩操作數字符串第i位和第j位乘積結果應存放在結果數組的i+j位(i, j從0開始)。2. 需要對結果數組從前往後進行一次檢查進位。

[阿拉伯數字與其中文讀音字符串互轉]

https://my.oschina.net/u/3321842/blog/1556816

  • 單位:四個一組讀數,組的單位[“億”, “萬”, “”], 組中的四位數字的對應的單位["千”,“百”,“十”,“”]
  • 不讀零的情況:四位一組,組中首位或末位是零的時候不讀;讀零的情況:組中非首位或末尾位&&左右非零簇擁之勢,如 x0xx, x0x0, xx0x, 0x0x, 其中x00x是個特例子,中間讀了兩次零需要合併。

[1071. Greatest Common Divisor of Strings]

📰

  • 字符串之間的最大公約數。如 ABCABC 與 ABC GCD: ABC, 這裏要求每個字符串是約數的若干重複。
  • 有公約子串的充要條件:(str1 + str2).equals(str2 + str1), 公約子串:str1.substring(0, gcd(str1.length(), str2.length()));。
  • 參考:整數的GCD: y == 0 ? x : gcd(y, x % y);

[686. Repeated String Match]

📰

  • 給定兩個字符串 A 和 B, 尋找重複疊加字符串A的最小次數,使得字符串B成爲疊加後的字符串A的子串,如果不存在則返回 -1。舉個例子,A = “abcd”,B = “cdabcdab”。return 3.
  • 暴力。注意:1. 最多重複次數 B.length()/A.length()+2 2. 字符串相等的判斷 equals 3. 字符串或StringBuilder的indexOf()方法

========== Integer 整數相關 ==========

[69. Sqrt(x)]

★ ❥ 📰

    1. 二分法開平方,循環條件是左指針<=右指針。注意中間比較的是x/mid 與 mid 的大小,避免溢出。返回的總是向下取整,因此取最終值時取右側指針(即使沒有直接命中的值,最終也是返回右側的值)。2. 牛頓迭代法。循環條件abs(ret*ret - x) > precision,迭代方程 ret = (ret + x / ret) / 2.0;

[829. Consecutive Numbers Sum]

★★★ 📰

  • Math. 從0到accSum(i)<=N, 滿足條件(N - accSum(i))%i == 0的即可得到一組。

[556. Next Greater Element III]

★★ 📰

  • e.g. 1234 -> 1243 -> 1324 -> 1342 … step1. from right to left, find first sa[i] < sa[i+1], step2. from right to left, find the one sa[k] which is just larget that sa[i], swap i k, step3. reverse i+1 ~ end

[365. Water and Jug Problem]

★★ ❥ 📰

  • 轉換爲GCD問題。如果兩個水壺的容積的最大公約數可以除儘想要的水值,那就ok.
  • ❥GCD最大公約數。y == 0 ? x : gcd(y, x % y);

算法思想


分治法、動態規劃、貪心算法、回溯和分支限界算法

  • 分治:水平劃分子問題,子問題獨立。
  • 動態規劃:每一步的決策結果依賴於當前的決策和歷史決策的結果。和遞歸的區別,重複的子問題只解決一次。
  • 貪心:每一步都做出當時看起來最優的解,適用於局部最優能到達全局最優的情況。
  • 回溯:對問題空間隱式樹的深度優先搜索,不滿足解的條件時回退到回溯點,重新再搜其他可能路徑。
    https://www.cnblogs.com/liangstudyhome/p/4427937.html

大數據相關問題

  • 1GB = 2^30B ~ 10億(1 billion), 1MB = 2^20B ~ 100萬(1 million), 1KB = 2^10B ~ 1千.
  • 4字節32bit整數,假設用一位bit表示一個整數的有無狀態,那麼整個整數空間需要的位數232~40億,也就是232/8= 512MB(8bit=1Byte)的空間。假設每個數用2個bit來表示,那可用表示四種狀態:00,01,10,11, 那就需要1GB的空間。

[基本思想]

- 分治:hash(x) -> 取模 | 取首/尾若干bits分桶 -> 各桶可在內存中處理。
- 內存中處理之前,一般需要使用hashmap/hashset做一下按鍵聚合,值一般是頻次等可度量的值。
- 內存中解決TopK問題(固定的數組或流):大頂堆/小頂堆
- 內存中解決第K/中位數問題:快速選擇算法。

Redis

  • Redis的五種數據對象類型:字符串REDIS_STRING、列表REDIS_LIST、哈希REDIS_HASH、集合REDIS_SET和有序集合REDIS_ZSET。這些對象都是有以下若干基本的數據結構實現:字符串、雙向鏈表linkedlist、字典HashTable、跳躍表skiplist、整數集合intset和壓縮列表ziplist. Redis數據庫的鍵總是一個字符串對象類型REDIS_STRING,而值則可以是五種類型中的任意一種。

[基礎數據結構]

  • Redis只會C字符串作爲字面量,大多數情況下使用簡單動態字符串SDS. SDS相比C中字符串優點:1.O(1)獲取字符串長度,2. 杜絕緩衝區溢出 3.減少字符串長度修改時所需的內存重分配次數 4. 二進制安全

  • 雙向鏈表linkedlist, 提供了高效的節點重排能力,以及順序性的節點訪問方式,並且可以通過增刪節點來靈活地調整鏈表的長度。是列表類型REDIS_LIST的底層實現之一:當列表鍵對應的值包含的元素數量較多或者包含的元素都是比較長的字符串時

  • 字典哈希表HashTable, 字典帶有兩個哈希表,一個平時用,一個在進行漸進式rehash時用(Murmurhash2,鏈地址法解決哈希值衝突)。是哈希REDIS_HASH和REDIS_SET的底層實現之一。

  • 跳躍表skiplist, 有序的數據結構,通過在每個節點中維護多層(1-32隨機)指向其他節點的指針,達到快速訪問節點的目的。平均O(lgN)最壞O(n)的複雜度查找節點。是有序集合ZSET的底層實現之一:集合元素數量比較多或者元素的成員member是比較長的字符串

  • 整數集合intset, 底層實現時數組,以有序無重複的方式保存元素,在有需要的時候,會根據添加元素的數據類型自動升級int32/int64(不支持自動降級), 是SET類型的底層實現之一:如果集合只包含數值元素,且集合的元素數量不多時。

  • 壓縮列表ziplist, 是一種爲節約內存開發的順序性數據結構,可以包含多個節點,每個節點是個字節數組或整數值。是列表REDIS_LIST和REDIS_HASH的底層實現之一。

[數據對象類型]

  • 字符串類型REDIS_STRING編碼上分int, embstr(短SDS), raw(長SDS).

  • 列表類型REDIS_LIST

    • ziplist, 元素數量較少,或者字符串的長度較短時
    • likedlist, 元素數量多,字符串長度較長
  • REDIS_HASH

    • ziplist
    • hashtable
  • REDIS_SET

    • intset
    • hashtable
  • REDIS_ZSET

    • ziplist
    • skiplist

[分佈式鎖]

  • setnx + expire

[回收策略]

  • volilate-lru, voliate-ttl, violate-random | allkeys…

[對象引用計數GC、內存共享]

[Cache with DB]

  • 讀場景:
    • hit cache: 直接讀緩存
    • cache miss: 讀DB, insert cache
  • 寫場景:
    • delete first: if exist same key
    • write db
    • mq, canal sync binlog -> update cache
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章