62.不同路径
视频讲解:动态规划中如何初始化很重要!| LeetCode:62.不同路径_哔哩哔哩_bilibili
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
思路:首先需要一个二维数组记录坐标(i,j),再思考走到终点dp[m-1][n-1]是需要从哪两个方向过来,再思考如何初始化,必须把第一行第一列全部初始化为1,因为只有一种路径,dp[0][0]==1,
也是因为只有保持不动这一种方式
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>>dp(m,vector<int>(n,0));//二维数组的定义
for(int i=0;i<n;i++)
{
dp[0][i]=1;
}
for(int j=0;j<m;j++)
{
dp[j][0]=1;
}//初始化第一行第一列为0,不能只初始化dp[0][0]
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];
}
};
- 时间复杂度:O(m × n) 因为两层for循环
- 空间复杂度:O(m × n) 因为使用二维数组
63. 不同路径 II
视频讲解:动态规划,这次遇到障碍了| LeetCode:63. 不同路径 II_哔哩哔哩_bilibili
给定一个
m x n
的整数数组grid
。一个机器人初始位于 左上角(即grid[0][0]
)。机器人尝试移动到 右下角(即grid[m - 1][n - 1]
)。机器人每次只能向下或者向右移动一步。网格中的障碍物和空位置分别用
1
和0
来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。返回机器人能够到达右下角的不同路径数量。
测试用例保证答案小于等于
2 * 109
。思路:与上面这道题思路相似,遍历障碍物二维数组,遇到障碍物,直接dp[i][j]=0,
语法上注意点:该函数传入的为vector<vector<int>>& obstacleGrid,不用手动输入次二维数组的数值,直接获取m,n
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { int m=obstacleGrid.size(); int n=obstacleGrid[0].size();//直接通过所传递数组进行读入 vector<vector<int>>dp(m,vector<int>(n,0)); if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1)return 0; dp[0][0]=1;//初始化 for(int i=0;i<m;i++) { for(int j=0;j<n;j++) { if(obstacleGrid[i][j]==1)dp[i][j]=0;//该点本身是障碍物,不能通过任何方式移动到障碍物上 else{ if(i>0) dp[i][j]+=dp[i-1][j]; if(j>0) dp[i][j]+=dp[i][j-1];} } } return dp[m-1][n-1]; } };
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { int m = obstacleGrid.size(); int n = obstacleGrid[0].size(); if (obstacleGrid[0][0] == 1 || obstacleGrid[m-1][n-1] == 1) return 0; vector<vector<int>> dp(m, vector<int>(n, 0)); // 初始化第一列 for (int i = 0; i < m; i++) { if (obstacleGrid[i][0] == 1) break; // 遇到障碍物,后续都无法通行 dp[i][0] = 1; } // 初始化第一行 for (int j = 0; j < n; j++) { if (obstacleGrid[0][j] == 1) break; dp[0][j] = 1; } // 计算路径数 for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (obstacleGrid[i][j] == 1) { dp[i][j] = 0; } else { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } } return dp[m-1][n-1]; } };
第二种解题方式重新初始化第一行,第一列,其余不变
假设我们有如下的
obstacleGrid
:0 0 1 0 0 1 0 0 0 0 0 0 初始化之后
dp[i][j]变成:
1 1 0 0
1 0 0 0
1 0 0 0表示遇到障碍物后停止初始化为1,保持为0,因为不可能有路径走到这里。
- 整数拆分
视频讲解:动态规划,本题关键在于理解递推公式!| LeetCode:343. 整数拆分_哔哩哔哩_bilibili
贪心算法,需要利用数学规律,尽可能拆分成更多的3,如果只余下4,则不进行拆分了
class Solution { public: int integerBreak(int n) { if(n==2)return 1; if(n==3)return 2; if(n==4)return 4; int result=1; while(n>4)//表示等于4的话就不用再分了,*4比*3*1更大 { n=n-3; result=result*3; } return result*n; } };
采用动态规划思路难点:重点在于找到关系式,dp[i-j]表示对i-j进行进一步拆分寻找最大值class Solution { public: int integerBreak(int n) { vector<int>dp(n+1,0);//默认初始化为0,但最好显式初始化 dp[2]=1;//dp[n]的含义是非常直接的,表示整数n的拆分后的乘积最大化,初始化 for(int i=3;i<=n;i++)//表示从3开始,计算3的整数拆分最大值 { for(int j=1;j<i;j++)//枚举法,表示n该如何拆 {dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));//max只能传递两个参数,使用两个max }} return dp[n]; } };
时间复杂度:O(n^2) 空间复杂度:O(n)
- .不同的二叉搜索树
视频讲解:动态规划找到子状态之间的关系很重要!| LeetCode:96.不同的二叉搜索树_哔哩哔哩_bilibili
给你一个整数
n
,求恰由n
个节点组成且节点值从1
到n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。选定某个数
BST 的特性要求,根节点i
作为根i
左侧的数[1, 2, ..., i-1]
必须都在 左子树。 右侧的数[i+1, i+2, ..., n]
必须都在 右子树。假设我们选
左子树有i
作为根:i-1
个节点(数值范围是[1, 2, ..., i-1]
)。 右子树有n-i
个节点(数值范围是[i+1, i+2, ..., n]
)。 左右子树的结构是 相互独立的,可以分别构造。假设
n = 3
我们有
1, 2, 3
三个数,分别尝试以它们为根:1
作为根- 左子树:
f(0) = 1
(空树)。 - 右子树:
[2, 3]
的f(2) = 2
。 - 总数:
f(0) * f(2) = 1 × 2 = 2
。
2
作为根- 左子树:
[1]
的f(1) = 1
。 - 右子树:
[3]
的f(1) = 1
。 - 总数:
f(1) * f(1) = 1 × 1 = 1
。
3
作为根- 左子树:
[1, 2]
的f(2) = 2
。 - 右子树:
f(0) = 1
(空树)。 - 总数:
f(2) * f(0) = 2 × 1 = 2
。
思路:枚举需要累加所有可能性,dp[0]=1,表示空树也是一种有效的bst树,不会导致后面丢失有效的方案
class Solution { public: int numTrees(int n) { vector<int>dp(n+1,0);//明确含义,dp[n]表示n个节点的二叉搜索树有多少种 dp[0]=1;//空树 dp[1]=1; for(int i=2;i<=n;i++){//计算dp[n] for(int j=1;j<=i;j++)//枚举,以哪个节点为根节点 { dp[i]+=dp[j-1]*dp[i-j];//左子树的可能性乘右子树的可能性 } } return dp[n]; } };