同向双指针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