动态规划(dp)
动态规划问题的一般形式就是求最值,求解动态规划的核心问题是穷举。
三要素
- 状态转移方程
- 是否具备重叠子问题
- 是否具备最优子结构
明确base case -> 明确状态 -> 明确选择->明确DP函数/数组的含义
总结:我把使用dp数组能解决的动态规划可一归纳为人生三大终极问题
1、我是谁
2、从哪来
3、到哪去
如图
解法框架
# 自顶向下递归的动态规划
def dp(状态1,状态2,...):
for 选择 in 所有可能的选择:
# 此时的状态已经因为做了选择而改变
result = 求最值(result , dp(状态1,状态2,...))
return result;
# 自底向上的动态规划
# 初始化base case
dp[0][0][...] = base case;
# 进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2)
回溯
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result add(路径)
return
for 选择 in 选择列表
做选择
backtrack(路径,选择列表)
撤销选择
BFS
// 计算从起点start 到终点 target 的最近距离
int BFS(Node start , Node target){
Queue<Node> q; //核心数据结构
Set<Node> visited; //避免走回头路
q.offer(start); //将起点加入队列
visited.add(start);
int step = 0; //记录扩散的步数
while (q not empty){
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for(int i = 0;i<sz; i ++){
Node cur = q.poll();
/* 是否到达中终点 */
if(cur in target) return step;
/* 将cur的相邻节点加入队列 */
for(Node x : cur.adj()){
if(x not in visited) {
q.offer(x);
visited.add(x);
}
}
}
/* 划重点:跟新步数 */
step++;
}
}
滑动窗口
/* 滑动窗口算法框架 */
void slidingWindow(string s) {
unordered_map<char,int> window;
int left = 0,right = 0;
while( right < s.size()){
//c 是将移入窗口的字符
char c = s[right];
//增大窗口
right++;
//进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d,%d)\n",left,right);
/***********************/
//判断左窗口是否需要收缩
while(window needs shrink) {
//d 是将移除窗口的字符
char d = s[left];
//缩小窗口
left++;
//进行窗口内数据的一系列更新
...
}
}
}
区间合并
class Solution {
public int[][] merge(int[][] intervals) {
LinkedList<int[]> res = new LinkedList<>();
// 按区间的 start 升序排列
Arrays.sort(intervals, (a, b) -> {
return a[0] - b[0];
});
res.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] curr = intervals[i];
// res 中最后一个元素的引用
int[] last = res.getLast();
if (curr[0] <= last[1]) {
last[1] = Math.max(last[1], curr[1]);
} else {
// 处理下一个待合并区间
res.add(curr);
}
}
return res.toArray(new int[0][0]);
}
}