例题:
准备了两道做法几乎相同的二维空间的dp题目
题目一:LeetCode-64最小路径和
Leetcode64
题目二:蓝桥杯-跳跃
跳跃
两者区别只在于子问题的个数,题目一的个数只有2个方向,而题目二有9个方向。
自顶向下的好处
- 有助于理解dfs、bfs等等算法。
- 相比自底向上来说,前期虽然痛苦,但是当你清楚结果体系,熟悉后,你会发现写dp递归法比递推法写的快得多。
- 容错性大,就是用递推方法的话,可能会被一些判断的小错误绊住脚,而递归方法只要base case不出差错,一般没有问题。
自顶向下的函数结构详解:
dp函数的结构(可结合代码画递归树理解)无非就是:
- 递归树的叶子结点的跳出条件->对应base case(决定子问题的深度(个数))、base case一般为最简单的题解子状态
- 递归函数的主体->对应状态转移方程(递归树的展开)。
- 非叶子结点(子问题)的跳出条件->将return结果写在递归树展开的后面即可 总的过程:通过状态转移方程不断将递归树延伸,直到触及base case停止延伸,往前执行之前的递归,这样就完成了每个非叶子结点的结果跳出,最终得到题解。
但要是这样写的话很快就会超时,原因是:递归树上可能大量结点重复计算,这个时候就需要一个备忘录一样的东西将以及计算好的题解保存,可将备忘录添加到base case中来,实际上我说的base case决定递归树的深度不完全正确,准确来说应该是决定子树的深度,因为递归过程实际上是一个入栈和出栈的过程,所以首先计算的是最深的那颗树。
划重点:
递归的结构:
- base case (决定递归树深度,叶子结点的结果)
- 状态转移方程(决定递归树的延伸方式)
- 处理完子问题的返回(决定非叶子结点的处理结果)
递归的原理:
这方面也没办法讲的明白,递归实际上就是一个后进先出的栈的形式,即递归就是入栈和出栈的过程,根据递归树结合深搜理解是最好的。
题目一(强烈建议用递归法):
DP函数递归解决:
int dp(vector<vector<int>>& grid,vector<vector<int>>&memo,int x,int y){
if(x<0||y<0) return 666;
//base case:
if(x==0&&y==0) return grid[x][y];
if(memo[x][y]!=-1) return memo[x][y];
//condition transfer:
int a = min(dp(grid,memo,x-1,y),dp(grid,memo,x,y-1));
//更新备忘录
memo[x][y] = a + grid[x][y];
return memo[x][y];
}
DP递推解决:
int dp(vector<vector<int>>& grid) {
int dx[] = {-1,0};
int dy[] = {0,-1};
int x,y;
for(int i=0;i<grid.size();i++){
for( int j=0;j<grid[i].size();j++){
int tmp = INT_MAX;
for(int k =0;k<2;k++){
x = i+dx[k];y=j+dy[k];
if(x>=0&&y>=0){
tmp = min(tmp,grid[x][y]);
}
}
if(tmp!=INT_MAX)
grid[i][j] += tmp;
}
}
return grid[grid.size()-1].back();
}
题目二(同样建议用递归法)
DP递归:
int dp(vector<vector<int>>&grid,vector<vector<int>>&memo,int x,int y){
int dx[] = {0,0,0,-1,-1,-1,-2,-2,-3};
int dy[] = {-1,-2,-3,0,-1,-2,0,-1,0};
//base case:
if(x<0||y<0)return INT_MIN;
if(x==0&&y==0)return grid[0][0];
if(memo[x][y]!=INT_MIN)return memo[x][y];
//condition transfer:
int temp = INT_MIN;
for(int i=0;i<9;i++){
int q = dp(grid,memo,x+dx[i],y+dy[i]);
if(q==INT_MIN)continue;
temp = max(temp,q);
}//备忘录memo更新
memo[x][y] = temp + grid[x][y];
return memo[x][y];
}
DP递推:
int dp(vector<vector<int>>&grid){
int n =grid.size();
int m =grid[0].size();
int x[9] = {0,0,0,-1,-1,-1,-2,-2,-3};
int y[9] = {-1,-2,-3,0,-1,-2,0,-1,0};
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
int temp = INT_MIN;
for(int t=0;t<9;++t)
{
if(i+x[t]>0 && j+y[t]>0){
temp = max(temp,grid[i+x[t]][j+y[t]]);
}
}
if(trans!=INT_MIN) grid[i][j]+=temp;
}
return grid[n-1][m-1];
}