方法
这里开始,跟着代码随想录大佬的脚步,刷leetcode
大佬给的五步曲和灵魂三问,还挺好使的
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
如果代码写出来了,一直AC不了,灵魂三问:
- 这道题目我举例推导状态转移公式了么?
- 我打印dp数组的日志了么?
- 打印出来了dp数组和我想的一样么?
哈哈,专治各种代码写出来了但AC不了的疑难杂症。
整数拆分
按照五部曲顺序思考这个问题
- 定义一个
dp[i]
,就表示n==i
时,取得的最大整数拆分乘积,简单的说就是答案
这道题的递推公式不难想
很容易能得出 dp[i] = (i-j)*dp[j]
那这就写对了?
再看看下面几个i
和dp[i]
当i==4
时,如果按照上面写的,dp[4] = (4-2)*dp[2] == 2
,与答案不符
而答案dp[4]==4
又是怎么来的?
明显是dp[i] = (i-j)*j
合并起来就是 dp[i] = (i-j)*max(dp[j],j)
- 所以五步曲的第二步完成了
- 初始化简单,前面几个数处理一下,后面遍历就方便了
- 遍历自然是从前往后
- 第五步,是当你写的程序AC不通过时,就写一遍上面的表格,对比程序打印的结果,查找代码的错误
好了,用五步曲真的很快啊,写这道题才花了十来分钟
class Solution {
public:
int integerBreak(int n) {
vector<int> dp(n+5,0);
dp[2] = 1;
dp[3] = 2;
if(n<4) return dp[n];
for(int i=4;i<=n;++i){
for(int j=2;j<i;++j){
dp[i] = max(dp[i],(i-j)*max(j,dp[j]));
}
}
return dp[n];
}
};
评论区有个思路很新奇的答案
impl Solution {
pub fn integer_break(n: i32) -> i32 {
[0, 0, 1, 2, 4, 6, 9, 12, 18, 27, 36, 54, 81, 108, 162, 243, 324, 486, 729,
972, 1458, 2187, 2916, 4374, 6561, 8748, 13122, 19683, 26244, 39366, 59049,
78732, 118098, 177147, 236196, 354294, 531441, 708588, 1062882, 1594323, 2125764,
3188646, 4782969, 6377292, 9565938, 14348907, 19131876, 28697814, 43046721,
57395628, 86093442, 129140163, 172186884, 258280326, 387420489, 516560652,
774840978, 1162261467, 1549681956][n as usize]
}
}
不同路径
按照五步曲走
- 明显这里需要建立二维的数组
dp[i][j]
,来表示(0,0)
到(i,j)
的路径数 - 要到达
(i,j)
只有两种方法,从(i-1,j)
或(i,j-1)
过去,所以递推公式就是
dp[i][j] = dp[i-1][j] + dp[i][j-1];
- 第一行和第一列的每一个格子,都只有一种方法,并不适用上面的递推公式
- 遍历顺序就横着走
- 这一步是检查代码结果和预期结果不同时,自己手写结果,再和代码结果对比,查找代码的错误点
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m,vector(n,0));
for(int i=0;i<m;++i){
dp[i][0] = 1;
}
for(int i=0;i<n;++i){
dp[0][i] = 1;
}
for(int i=1;i<m;++i){
for(int j=1;j<n;++j){
dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
};
大佬还用一维数组做出来了
空间优化
因为每次只需要从上面的位置和左边的位置转移过来,可以优化空间
只用一个一维数组,滚动更新每行的路径数量
此时dp[j]含义为:第某行第j列的路径数量为dp[j]
转移方程:dp[j] = dp[j] + dp[j - 1]
第i行第j列位置的路径数量 = 第i - 1行第j列位置的路径数量 + 第i行第j - 1列位置的路径数量
意思就是,dp[i][j] = dp[i-1][j] + dp[i][j-1]
,看似是需要加两个数,但假如,我把dp[i][j-1]
的数搬下来,变成了dp[i][j]
,然后再加上左边的数dp[i-1][j]
,结果是一样的
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n);
for (int i = 0; i < n; i++) dp[i] = 1;
for (int j = 1; j < m; j++) {
for (int i = 1; i < n; i++) {
dp[i] += dp[i - 1];
}
}
return dp[n - 1];
}
};
不同路径 II
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int rows = obstacleGrid.size();
int cols = obstacleGrid[0].size();
vector<vector<int>> dp(rows,vector(cols,0));
for(int i=0;i<rows;++i)
{
if(obstacleGrid[i][0]==1) break;
dp[i][0] = 1;
}
for(int j=0;j<cols;++j){
if(obstacleGrid[0][j]==1) break;
dp[0][j] = 1;
}
for(int i=1;i<rows;++i){
for(int j=1;j<cols;++j){
if(obstacleGrid[i][j]==1||obstacleGrid[i-1][j]==1&&obstacleGrid[i][j-1]==1) continue;
if(obstacleGrid[i-1][j]) dp[i][j] = dp[i][j-1];
else if(obstacleGrid[i][j-1]) dp[i][j] = dp[i-1][j];
else dp[i][j] = dp[i-1][j]+dp[i][j-1];
}
}
return dp[rows-1][cols-1];
}
};
这就懒得写了,但自己分析得复杂了,大佬的很简单