同向雙指針java
相向雙指針node
利用前綴和下降時間複雜度算法
全局prev變量、全局sum變量數組
樹的path sum相關數據結構
LCA(最近公共祖先)指針
left
和right
,而後更新全局res
的時候是left + right
,可是本次遞歸返回的是max(left, right)
二叉查找樹要麼是一棵空樹,要麼是一棵具備以下性質的非空二叉樹:code
- 若左子樹非空,則左子樹上的全部結點的關鍵字值均小於根結點的關鍵字值
- 若右子樹非空,則右子樹上的全部結點的關鍵字值均大於根結點的關鍵字值
- 左、右子樹自己也分別是一棵二叉查找樹(二叉排序樹)
能夠看出,二叉查找樹是一個遞歸的數據結構,且對二叉查找樹進行中序遍歷,能夠獲得一個遞增的有序序列排序
好比lc 98題,不使用全局變量,採用遞歸左右子樹的形式,判斷BST :遞歸
class Solution { public boolean isValidBST(TreeNode root) { return helper(root, null, null); } private boolean helper(TreeNode node, Integer max, Integer min) { if (node == null) return true; if (min != null && node.val <= min) return false; if (max != null && node.val >= max) return false; return helper(node.left, node.val, min) && helper(node.right, max, node.val); } }
87.Scramble String、140. Word Break、329. Longest Increasing Path in a Matrix、 638. Shopping Offers、935.Knight Dialerip
class Solution { int m, n; int[][] dirs = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}}; public int longestIncreasingPath(int[][] matrix) { if (matrix.length == 0 || matrix[0].length == 0) return 0; m = matrix.length; n = matrix[0].length; int[][] memo = new int[m][n]; int res = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { int tmp = helper(matrix, i, j, memo); res = Math.max(res, tmp); } } return res; } private int helper(int[][] matrix, int x, int y, int[][] memo) { if (memo[x][y] != 0) return memo[x][y]; int res = 1; for (int[] d : dirs) { int nx = x + d[0]; int ny = y + d[1]; if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue; if (matrix[nx][ny] <= matrix[x][y]) continue; int tmp = helper(matrix, nx, ny, memo) + 1; res = Math.max(tmp, res); } memo[x][y] = res; return res; } }
比較典型的dfs + memo: 935.Knight Dialer
可是書已到這題的memo是二維的,也就是每一步的狀態,其實涉及到兩個變量,即每一步按到哪一個數,和當前是第幾個數,因此是
memo[num][index]
,飯容易範這個錯誤,將memo寫成以維的memo[num]
class Solution { int[][] neighbors = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {0, 3, 9}, {}, {0, 1, 7}, {2, 6}, {1, 3}, {2, 4}}; int MOD = 1_000_000_007; public int knightDialer(int N) { int res = 0; int[][] memo = new int[10][N + 1]; for (int i = 0; i < 10; i++) { res += helper(i, N, memo); res %= MOD; } return res; } private int helper(int num, int index, int[][] memo) { if (index == 1) return 1; if (memo[num][index] != 0) return memo[num][index]; int res = 0; for (int next : neighbors[num]) { res += helper(next, index - 1, memo); res %= MOD; } memo[num][index] = res; return res; } }
0.記得加上visited數組,防止重複訪問而產生overflow
樹、圖的層序遍歷
dynamic programming的適用狀況
最優子結構:問題能夠被分解爲求解子問題的最優解,也就是如今的解依賴於子問題的左右解
子問題重複計算 :子問題具備重複求解行,因此能夠事先存儲下來,以便於以後直接獲取,從而避免子問題對的重複求解
無後效性:子問題的最優解是肯定的,且一旦子問題的最優解獲得後,原問題的最優解能夠用子問題的最優解求解
Matrix DP
典型題:LIS (注意LIS的兩種姿式)
最小調整代價
2 sets of subproblems: 原問題的最優解依賴於兩個子問題的最優解 (一般從左向右掃描一次,而後再從右向左掃描一次,最後再合併左右的結果)
for (int i = 1; i < A.length; i++) { if (A[i] > A[i - 1]) inc[i] = inc[i - 1] + 1; } for (int i = A.length - 2; i > 0; i--) { if (A[i] > A[i + 1]) dec[i] = dec[i + 1] + 1; } //合併 for (int i = 0; i < A.size(); i++) { if (inc[i] && dec[i]) { res = Math.max(res, inc[i] + dec[i] + 1); } }
public int maxProduct(int[] nums) { if (nums == null || nums.length == 0) return 0; int n = nums.length; int[] mins = new int[n]; int[] maxs = new int[n]; int res = nums[0]; mins[0] = nums[0]; maxs[0] = nums[0]; for (int i = 1; i < n; i++) { maxs[i] = mins[i] = nums[i]; if (nums[i] > 0) { maxs[i] = Math.max(maxs[i - 1] * nums[i], nums[i]); mins[i] = Math.min(mins[i - 1] * nums[i], nums[i]); } else if (nums[i] < 0) { maxs[i] = Math.max(mins[i - 1] * nums[i], nums[i]); mins[i] = Math.min(maxs[i - 1] * nums[i], nums[i]); } res = Math.max(res, maxs[i]); } return res; }
/** 題意:求乘積最大的子數組,關鍵是原數組中有正負數 分析:那麼確定是用動態規劃了,本質是用兩個dp數組,一個維護至今的最大值,一個維護至今的最小值 ; 想的簡單點,就是維護一個至今的最大值和最小值數組,max[i]表示到i爲止的最大的,min[i]表示迄今最小值 固然簡化了也能夠用兩個變量max和min,也就是用來個狀態來維護,只須要當A[i] < 0的時候交換min和max就好了 若是是負數,則就會使獲得當前爲止的最大值變爲最小值,當前爲止的最小值變爲最大值; 應該維護兩個變量,一個是至今的最大值一個至今的最小值,而後還有一個全局的最大值; */ //兩個dp數組版本: public int maxProduct(int[] nums) { if (nums == null || nums.length == 0) return 0; int n = nums.length; int[] mins = new int[n]; int[] maxs = new int[n]; int res = nums[0]; mins[0] = nums[0]; maxs[0] = nums[0]; for (int i = 1; i < n; i++) { maxs[i] = mins[i] = nums[i]; if (nums[i] > 0) { maxs[i] = Math.max(maxs[i - 1] * nums[i], nums[i]); mins[i] = Math.min(mins[i - 1] * nums[i], nums[i]); } else if (nums[i] < 0) { maxs[i] = Math.max(mins[i - 1] * nums[i], nums[i]); mins[i] = Math.min(maxs[i - 1] * nums[i], nums[i]); } res = Math.max(res, maxs[i]); } return res; } //兩個狀態變量: public int maxProduct(int[] nums) { if (nums == null || nums.length == 0) return 0; int n = nums.length; int res = nums[0], tmpMin = res, tmpMax = res; for (int i = 1; i < n; i++) { if (nums[i] < 0) { int tmp = tmpMin; tmpMin = tmpMax; tmpMax = tmp; } tmpMax = Math.max(tmpMax * nums[i], nums[i]); tmpMin = Math.min(tmpMin * nums[i], nums[i]); res = Math.max(res, tmpMax); } return res; }
dp[i][0]、dp[i][1] dp[i][2] ...i is problem size
揹包問題
單調棧
遞歸問題轉爲迭代形式(不少遞歸問題都也能夠用stack解決)
用棧模擬:根據題目的性質,這時候分析幾個例子,查看是否具備棧的性質,好比和棧頂元素關係直接這種狀況
HashMap
TreeMap
有序key-value,通常按照key有序組織,能夠找到第一個比當前key小的:
floorKey()
或者大的key:ceilingKey()
,在有些題目中頗有用
k largest ...注意O(klogk)解法
private ListNode reverse(ListNode head){ ListNode newNode=null; while(head!=null){ ListNode temp=head.next; head.next=newNode; newNode=head; head=temp; } return newNode; }
幾個基本操做:相似題目題目23四、25能夠分解爲這幾個基本操做:求鏈表中點(求鏈表第n個點)、反轉鏈表
相似題目24須要先後兩個指針prev、cur來交替操做
相似題目8六、328屬於分割鏈表,藉助dummy node
並查集模版
class UF { int[] parent; public UF(int N) { parent = new int[N]; for (int i = 0; i < N; i++) parent[i] = i; } public int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); return parent[x]; } public void union(int x, int y) { parent[find(x)] = find(y); } }
Trie
Graph
貪心法
分治法
Segment Tree