不同路径
题解
1. 状态表示:dp[i][j]表示以[i,j]位置为结尾时的方法数
2. 状态转移方程根据最近的一步划分问题**,可以从两个方向到达最后一个位置(上边和右边)
dp[i][j] = dp[i-1][j] + dp[i][j-1]
3. 初始化:虚拟节点的值保证后边的填表是正确的,例如下面这张图,下表的映射关系,给的数组映射dp表中的值,数组的横纵坐标都要减一
4. 填表顺序:从上往下填写每一行,从左往右填写每一列
5. 返回值:返回最后一个dp表,dp[m][n]
代码
class Solution
{
public:
int uniquePaths(int m, int n)
{
// 到达dp[i][j]位置有多少种方法
vector<vector<int>> dp(m+1,vector<int>(n+1));
dp[1][0] = 1;
// 保证里面的一圈被初始化为1
for(int i = 1;i <= m;i++)// 从上往下遍历每一行
{
// 从左往右填写每一列
for(int j = 1;j <= n;j++)
{
// 状态转移方程
// 不用dp[i-1][j] + 1
// 因为方法数走到下一个位置是步数加1不是方法加1
// dp[i][j] = dp[i-1][j]
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m][n];
}
};
地下城游戏
题解
1. 状态表示:dp[i][j]要以[i,j]位置为起点,达到终点(这题是开始的第一个格子)的最低健康点数
2. 状态转移方程:
dp[i][j] = min(dp[i+1][j],dp[i][j+1]) - d[i][j]
dp[i][j] = max(1,dp[i][j])
x 为该点的最低健康点数,dp[i][j]
x + d[i][j] >= dp[i][j+1](往右走的) 该点的最低能量必须比下一个点的最低能量要大,才能走到下一个点
x >= dp[i][j+1] - d[i][j],如果x为负数,说明之前状态的最低健康点数是负数也能往下走,这是不符合逻辑的,所以要给这个点最低的存活值1
3. 初始化:走到最后一格后的健康值最低为1才能存活,并且需要两格初始化最后一个点,其他格保证不影响结果都初始化为最大值,因为选的是较小的能量值
代码
class Solution
{
public:
int calculateMinimumHP(vector<vector<int>>& dun)
{
// 返回最低初始健康点数
// 如果他的健康点数在某一时刻降至 0 或以下(>=1),他会立即死亡
// dp[i][j]表示到达[i,j]位置时的最小
// 选择下右路径中大的那个点,不是返回右下角这个点
// 返回的是中途遇到的负数的最小值的那个点
// 下一个点的最低健康点数要比上一个点的要小
// 比如在上一个点扣血后的最低健康点数要比下一个点的最低健康点数要大,才能来到下一个点
int n = dun.size(),m = dun[0].size();
vector<vector<int>> dp(n+1,vector<int>(m+1,INT_MAX));
dp[n][m-1] = dp[n-1][m] = 1;
// 到达最后一个位置需要的最低血量
for(int i = n-1;i >= 0;i--)// 从下往上
{
for(int j = m-1;j >= 0;j--)// 从右往左
{
dp[i][j] = min(dp[i+1][j],dp[i][j+1]) - dun[i][j];
dp[i][j] = max(dp[i][j],1);
// 下一个点的最低健康点数如果是负数是不合法的,最低
// 需要1的健康点数
}
}
return dp[0][0];
}
};
不同路径II
题解
1. 这题和第一题很像,状态表示和状态转移方程都是一样的
2. 唯一要注意的是原数组中为1的点是障碍物,如果路径上有障碍物,这条路径是不算进结果里的,这个点是0
3. 加个判断如果原数组中该点是0就可以算进方法数中
代码
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>>& ob)
{
// dp[i] 表示的是以[i,j]为结尾的方法数
// 状态表示
// 状态转移方程
// 初始化
// 返回值
// 多开一个节点存0,防止初始化太麻烦
// 这样就要注意映射关系
int m = ob.size(),n = ob[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
dp[1][0] = 1;
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
if(ob[i-1][j-1] == 0) dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m][n];
}
};
最小路径和
题解
1.状态表示:dp[i][j]表示到达以[i,j]位置为结尾的总和的最小值
2.状态转移方程:
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1]
3. 初始化:都初始化为0
4. 填表顺序:从上往下,从左往右
5. 返回值:返回最后一个位置的值dp[m][n]
代码
class Solution
{
public:
int minPathSum(vector<vector<int>>& grid)
{
//状态表示 dp[i][j] 标志到达[i][j]位置的最小加和
//状态转移方程:
//dp[i][j] = min(dp[i][j-1],dp[i-1][j]) + grid[i-1][j-1]
int m = grid.size(),n = grid[0].size();
vector<vector<int>> dp(m + 1,vector<int>(n+1,INT_MAX));
// 初始化,dp[0][1]初始化为0保证第一个数加和为它本身
dp[0][1] = 0;
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
return dp[m][n];
}
};
下降路径最小和
题解
1. 状态表示:以[i,j]位置为结尾的最小下降路径的值
2. 状态转移方程:
dp[i][j] = min(dp[i-1][j],dp[i-1][j-1],dp[i-1][j+1]) + mt[i-1][j-1]
3. 初始化:把最上面一层初始化为0,旁边两侧初始化为最大值,防止第一层和旁边两侧影响dp表中的值
4. 填表顺序:从上往下,从左往右
5. 返回值:返回dp表最后一层的最小值就是最小下降路径的和
代码
class Solution
{
public:
int minFallingPathSum(vector<vector<int>>& mt)
{
// 状态表示:以i位置为结尾时的下降的最小值
// 状态转移方程:
// dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1]) + m[i][j];
// 先初始化第一行的最小的,再向下找这个数向下周围的三个之中最小的
// 这题我竟然理解为了只求每一行填一个dp,然后用i,j去下面找下三个中的最小值m,太离谱了
// 正解应该是求出每个dp,保证每个dp都是当前数和上一行的左中右,中最小的加和
// 这样只需要找最后一行的最小dp即可
int n = mt.size();
vector<vector<int>> dp(n+1,vector<int>(n+2,INT_MAX));
// 初始化
for(int i = 0;i <= n+1;i++) dp[0][i] = 0;
for(int i = 1;i <= n;i++)
{
for(int j = 1;j <= n;j++)
{
dp[i][j] = min(dp[i-1][j],min(dp[i-1][j-1],dp[i-1][j+1])) + mt[i-1][j-1];
}
}
int ret = INT_MAX;
for(int i = 1;i <= n;i++)
{
ret = min(ret,dp[n][i]);
}
return ret;
}
};
LCR 166. 珠宝的最高价值
题解
1. 状态表示:以[i,j]位置为结尾时,珠宝的最大价值
2. 状态转移方程:
dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + frame[i-1][j-1]
3. 初始化:将dp表都初始化为0,为了防止影响珠宝的最终价值
4. 填表顺序:从上往下,从左往右
5. 返回值:返回最后一个位置的dp值
代码
class Solution
{
public:
int jewelleryValue(vector<vector<int>>& frame)
{
// 记录一下第一次自己做出来dp路径问题而且一次对
// dp[i][j]表示[i,j]位置的最大礼物价值
// dp[i][j] = max(dp[i-1][j]+frame[i-1][j-1],dp[i][j-1]+frame[i-1][j-1])
int m = frame.size(),n = frame[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
for(int i = 1;i <= m;i++)
{
for(int j = 1;j <= n;j++)
{
dp[i][j] = max(dp[i-1][j]+frame[i-1][j-1],
dp[i][j-1]+frame[i-1][j-1]);
}
}
return dp[m][n];
}
};
总结
1. 路径问题对于状态表示:要么以[i,j]位置为结尾,根据题意(题目经验),根据前面的状态来填[i,j]状态,要么以[i,j]位置为起点,根据题意(题目经验),根据后面的状态来填[i,j]状态
2. 状态转移方程:根据最近的一步划分问题
3. 初始化:要么初始化上左两层,要么初始化左右上三层,要么初始化右下两层
4. 填表顺序:要么从上往下,从左往右,要么从下往上,从右往左
5. 返回值:要么返回最后一格的值,要么最后一层的某个格的值,要么返回第一格的值