1 动态规划框架
(1)定义变量(一直变化的值,无限一定不是变量)
(2)写状态转移方程
(3)求最优值
(4)确定边界
1.1 子序列
(1)最大子序和
(2)最长上升子序列
(3)最大回文子序列
(4)最长公共子序列
(5)二维上升子序列
(6)编辑距离
int n = array.length;
int[] dp = new int[n];
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
dp[i] = 最值(dp[i], dp[j] + ...)
}
}
int n = arr.length;
int[][] dp = new dp[n][n];
for (int i = 0; i < n; i++) {
for (int j = 1; j < n; j++) {
if (arr[i] == arr[j])
dp[i][j] = dp[i][j] + ...
else
dp[i][j] = 最值(...)
}
}
1.2 背包
(1)0-1背包
(2)完全背包
(3)子集划分
(4)零钱兑换
0-1
for i in [1..N]:
for w in [1..W]:
dp[i][w] = max(
dp[i-1][w],
dp[i-1][w - wt[i-1]] + val[i-1]
)
return dp[N][W]
完全
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i-1] >= 0)
dp[i][j] = dp[i - 1][j]
+ dp[i][j-coins[i-1]];
return dp[N][W]
1.3 路径
// 状态转移
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = Math.min(
dp[i - 1][j],
dp[i][j - 1]
) + grid[i][j];
}
}
// 定义:从 src 出发,k 步之内到达 s 的最短路径权重
int dp(int s, int k) {
// base case
if (s == src) {
return 0;
}
if (k == 0) {
return -1;
}
// 初始化为最大值,方便等会儿取最小值
int res = Integer.MAX_VALUE;
if (indegree.containsKey(s)) {
// 当 s 有入度节点时,分解为子问题
for (int[] v : indegree.get(s)) {
int from = v[0];
int price = v[1];
// 从 src 到达相邻的入度节点所需的最短路径权重
int subProblem = dp(from, k - 1);
// 跳过无解的情况
if (subProblem != -1) {
res = Math.min(res, subProblem + price);
}
}
}
// 如果还是初始值,说明此节点不可达
return res == Integer.MAX_VALUE ? -1 : res;
}
1.4 打家劫舍
int rob(int[] nums) {
int n = nums.length;
// dp[i] = x 表示:
// 从第 i 间房子开始抢劫,最多能抢到的钱为 x
// base case: dp[n] = 0
int[] dp = new int[n + 2];
for (int i = n - 1; i >= 0; i--) {
dp[i] = Math.max(dp[i + 1], nums[i] + dp[i + 2]);
}
return dp[0];
}
1.5 股票买卖
1.6 最小次数
1.7 游戏问题(戳气球、扔鸡蛋)
1.8.1 戳气球
1.9 贪心算法
1.9.1 区间
1、排序。常见的排序方法就是按照区间起点排序,或者先按照起点升序排序,若起点相同,则按照终点降序排序。当然,如果你非要按照终点排序,无非对称操作,本质都是一样的。
2、画图。就是说不要偷懒,勤动手,两个区间的相对位置到底有几种可能,不同的相对位置我们的代码应该怎么去处理。
场景1:求最少的不相交区间
根据end升序排序,存在交集,每次选择最小的end。
int count = 1;
int curEnd = intvs[0][1];
for (int[] intv : intvs) {
int start = intv[0];
if(curEnd <= start){
count++;
x_end = interval[1];
}
}
场景2:使用最少的短区间拼接长区间
按照start升序排列,存在交集,每次选择最大的end
for (int[] clip : clips) {
while (i < n && clip[0] <= curEnd) {
nextEnd = Math.max(clip[1], nextEnd);
i++;
}
// 找到下一个视频,更新 curEnd
res++;
curEnd = nextEnd;
if (curEnd > time) {
return res;
}
}
场景3:给你输入若干时间区间,让你计算同一时刻「最多」有几个区间重叠。
分成两个数组,一个按照start排序,一个按照end排序,遇到start 就count+1,遇到end就count-1
// 扫描过程中的计数器
int count = 0;
// 双指针技巧
int res = 0, i = 0, j = 0;
while (i < n && j < n) {
if (begin[i] < end[j]) {
// 扫描到一个红点
count++;
i++;
} else {
// 扫描到一个绿点
count--;
j++;
}
// 记录扫描过程中的最大值
res = Math.max(res, count);
}
return res;
}
1.9.2跳跃游戏
2 回溯框架
public void backTrack(nums,track) {
for (int i = 0; i < nums.length; i++) {
if (满足了条件) {
res.add(track);
}
//剪枝
track.add(nums[i]);
backTrack(nums);
track.remove(nums[i]);
}
}
3 BFS框架
public void bfs(TreeNode root) {
Queue<TreeNode> queue = new LinkedList();
queue.add(root);
while (!queue.isEmpty()) {
//该层元素个数
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
}
4 树遍历框架
// 二叉树遍历框架
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
}
5 DFS框架(二维)
void dfs(int[][] grid, int i, int j, boolean[][] visited) {
int m = grid.length, n = grid[0].length;
if (i < 0 || j < 0 || i >= m || j >= n) {
//越界
return;
}
if (visited[i][j]) {
//已遍历
return;
}
visited[i][j] = true;
//上
dfs(grid, i - 1, j, visited);
//下
dfs(grid, i + 1, j, visited);
//左
dfs(grid, i, j - 1, visited);
//右
dfs(grid, i, j + 1, visited);
}
6 滑动窗口框架
public void slidingWindow(String s) {
//定义窗口
StringBuilder window;
int left = 0;
int right = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(right);
window.append(c);
if (window 需要缩减){
char d = s.charAt(left);
left++;
}
}
}
7 二分搜索框架
int binary_search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
}
8 反转链表框架
int reverse(ListNode node) {
ListNode pre = null, cur = node, nxt = node;
while (cur.next != null) {
nxt = cur.next;
cur.next = pre;
pre = cur;
cur = nxt;
}
}
9 移除相同元素框架
int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
int slow = 0, fast = 0;
while (fast < nums.length) {
if (nums[fast] != nums[slow]) {
slow++;
// 维护 nums[0..slow] 无重复
nums[slow] = nums[fast];
}
fast++;
}
}
10 图遍历
void traverse(Graph graph, int s) {
if (visited[s]) return;
// 经过节点 s
visited[s] = true;
for (TreeNode neighbor : graph.neighbors(s))
traverse(neighbor);
}
11 区间相关
(1)排序
(2)取反
12 快速排序框架
void sort(int[] nums, int lo, int hi) {
if (lo > hi) return;
// 通过交换元素构建分界点索引 p
int p = getIndex(nums, lo, hi);
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
```java
private int getIndex(int[] nums, int lo, int hi) {
int pivot = nums[lo];
while (lo < hi) {
while (lo < hi && nums[hi] > pivot) hi--;
nums[lo] = nums[hi];
while (lo < hi && nums[lo] < pivot) lo++;
nums[hi] = nums[lo];
}
nums[lo] = pivot;
return lo;
}
13 单调栈
List<Integer> nextGreaterElement(int[] nums) {
for (int i = nums.length - 1; i >= 0; i--) {
if (!stack.isEmpty() && nums[i] > stack.peek()) {
stack.pop();
}
// nums[i] 身后的 next great number
res[i] = s.empty() ? -1 : s.top();
stack.push(nums[i]);
}
return res;
}
14 前缀和
List<Integer> prefixSum(int[] nums) {
sum[0] = 0;
for (int i = 0; i < nums.length; i++) {
sum[i + 1] = sum[i] + nums[i];
}
// sum of nums[j..i-1]
sum[i] - sum[j]
}
15 差分数组
public class Difference {
private int[] diff;
public Difference(int[] nums) {
assert nums.length > 0;
diff = new int[nums.length];
// 构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
}
public void increment(int i, int j, int val) {
diff[i] += val;
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
public int[] result() {
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
}
16 nSum
两数之和
public List<List<Integer>> twoSum(int[] nums, int target) {
Arrays.stream(nums).sorted();
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) {
int sum = nums[lo] + nums[hi];
int left = nums[lo], right = nums[hi];
if (sum < target) {
lo++;
} else if (sum > target) {
hi--;
} else {
res.add({left,right});
while (lo<hi && nums[lo] == left) lo++;
while (lo < hi && nums[hi] == right) hi--;
}
}
}
三数之和(其他的依次类推)
/* 计算数组 nums 中所有和为 target 的三元组 */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
// 数组得排个序
sort(nums.begin(), nums.end());
int n = nums.size();
vector<vector<int>> res;
// 穷举 threeSum 的第一个数
for (int i = 0; i < n; i++) {
// 对 target - nums[i] 计算 twoSum
vector<vector<int>>
tuples = twoSumTarget(nums, i + 1, target - nums[i]);
// 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
for (vector<int>& tuple : tuples) {
tuple.push_back(nums[i]);
res.push_back(tuple);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while (i < n - 1 && nums[i] == nums[i + 1]) i++;
}
return res;
}
17 区间算法