不同路径
- 力扣题目链接
- 动态规划问题最重要的事情就是状态转移方程!
- 先来试下暴力深搜(超时警告⚠
int count=0;
void dfs(int x, int y, int m, int n){
if(x==m && y==n) {count++; return;}
if(x>m || y>n) return;
dfs(x+1, y, m, n);
dfs(x, y+1, m, n);
}
int uniquePaths(int m, int n) {
dfs(1, 1, m, n);
return count;
}
- 上面解法的问题就是递归可能爆栈了,事实上,只需要dp避免诸多重复计算
#define N 100
int dp[N][N];
int uniquePaths(int m, int n) {
for(int i=0; i<m; i++) dp[i][0]=1;
for(int j=0; j<n; j++) dp[0][j]=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];
}
- 空间优化版:
#define N 100
int dp[N];
int uniquePaths(int m, int 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];
}
- 组合数版本:C(m+n-2, m-1)
int uniquePaths(int m, int n) {
if(m==1||n==1) return 1;
long long numerator=1; // 分子
int denominator=m-1; // 分母
int count=m-1;
int t=m+n-2;
while(count--){
numerator*=(t--);
while(denominator!=0 && numerator%denominator==0){
numerator/=denominator;
denominator--;
}
}
return numerator;
}
不同路径Ⅱ
- 力扣题目链接
- 这道题目开始有障碍物了,有点小区别但不多,碰到障碍物之后保持原来状态
#define N 100
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if(obstacleGrid[0][0]==1) return 0;
int m=obstacleGrid.size(), n=obstacleGrid[0].size();
int dp[N]={0};
for (int j=0; j<n && obstacleGrid[0][j]==0; j++)
dp[j]=1;
for (int i=1; i<m; i++)
for (int j=0; j<n; j++)
if (obstacleGrid[i][j]==1) dp[j] = 0;
else if (j!=0) dp[j]+=dp[j-1];
return dp[n-1];
}
整数拆分
- 力扣题目链接
- 这道题目的dp方程需要推导明白,事实上,dp[0]和dp[1]不是必须的
#define N 59
int dp[N]={0};
int integerBreak(int n) {
dp[2]=1;
for(int i=3; i<=n; i++)
for(int j=1; j<i-1; j++) // 事实上,这里≤i/2就好
dp[i]=max(dp[i], max((i-j)*j, dp[i-j]*j));
return dp[n];
}
- 贪心法:不断取3即可,数学不论证
不同的二叉搜索树
- 力扣题目链接
- 这道题目看似有些抽象,但是我们通过观察n=3的情况
- 不难看出dp[3]=dp[2]*dp[0]+dp[1]*dp[1]+dp[0]*dp[2]
#define N 20
int dp[N]={1};
int numTrees(int n) {
for(int i=1; i<=n; i++)
for(int j=1; j<=i; j++)
dp[i]+=dp[j-1]*dp[i-j];
return dp[n];
}
- 综合以上题目,dp实际上就是记忆化搜索,用空间换时间!
参考资料
[1] 代码随想录
[2] Leetcode题解