动态规划算法
1、应用场景——背包问题
1、背包问题:有一个背包,容量为4磅,现有如下物品
2、要求:达到的目标为装入的背包的总价值最大,并且重量不超出,物品不能重复
2、动态规划算法介绍
1、动态规划算法的核心思想是:将大问题划分为小问题解决,从而一步步获取最优解的处理算法
2、动态规划算法和分支算法类似,及基本思想就是将待求解问题分解成若干子问题,先求子问题,然后从这些子问题的解得到原问题的解
3、与分治法不同的是,适合用于动态规划求解的问题,经分解得到的子问题往往不是互相独立的.(即下一个阶段的求解是建立在上一个阶段的解的基础上进行进一步求解)
4、动态规划可以通过填表的方式逐步推进,得到最优解
3、动态规划的三个步骤
动态规划就是利用历史数据,来避免我们重复计算,而这些历史记录,我们需要一些记录来保存,一般用一维数组或二维数组来保存。
1、定义数组元素的含义
2、找出数组元素之间的关系式
3、找出初始值
4、62. 不同路径
1、步骤
1、定义数组的含义:dp[i] [j] 表示从start走到 (i,j)这个位置的不同路径,最终 dp[m-1] [n-1] 就是我们要的答案
2、找出数组元素之间的关系式:dp[i] [j]=dp[i-1] [j] + dp[i] [j-1];走到(i,j)有两种走法,要么从左边来的,要么从上面来的,在这两种走法的和就是我们的答案
3、找出初始值:dp[0] [1]=1,dp[1] [0]=1;也就是start 位置的右边和左边只需要一步。我们还应该将 其他 start 所在的行和列的路径求出来,因为你在关系式中无法求 dp[0] [n] 和 dp[n] [0] 位置的路径(dp[0-1] [n] 数组下边不对)
2、代码实现
public static int uniquePaths(int m, int n) {
int[][] dp=new int[m+1][n+1];
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];
}
3、执行结果
5、64. 最小路径和
1、步骤
1、定义数组:dp[i] [j] 表示从左上角到(i,j)这个位置最小路径和。
2、数组元素之间的关系式:dp[i] [j] =min{ dp[i-1] [j],dp[i] [j-1]},要么从左边来的,要么从上面来的,在这两种中找最小值就是我们需要的
3、初始化值:关系式中必须满足 i>0 和 j>0 ,所以dp[o] [n] 和 dp[n] [0] 需要初始化
2、代码实现
public static int minPathSum(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
int[][] dp=new int[m][n];
dp[0][0]=grid[0][0];
//左边初始化
for(int i=1;i<m;i++){
dp[m][0]=dp[m-1][0]+grid[m][0];
}
//上面初始化
for(int i=1;i<n;i++){
dp[0][n]=dp[0][n-1]+grid[0][n];
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
6、72. 编辑距离
1、步骤
1、定义数组含义:dp[i] [j] 表示 word1 的前 i 个字母和 word2 的前 j 个字母之间的编辑距离
2、数组元素之间的关系式:当 word1[i] = word2[j] ,就不需要操作即 dp[i] [j]=dp[i-1] [j-1];
当 word1[i] != word2[j] 时就有三种操作(在这三种操作的最小值):
1、替换操作:dp[i] [j] = dp[i-1] [j-1]+1
2、插入操作:dp[i] [j] = dp[i] [j-1] +1,
3、删除操作:dp[i] [j] = dp[i-1] [j] +1,
3、初始化值:即当 word1 或 word2 为空时,增加删除操作
public static int minDistance(String word1, String word2) {
int m=word1.length();
int n=word2.length();
if(m==0||n==0){
return m+n;
}
int[][] dp=new int[m+1][n+1];
for(int i=0;i<=m;i++){
dp[i][0]=i;
}
for(int i=0;i<=n;i++){
dp[0][i]=i;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else {
dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
}
for (int[] ints : dp) {
for (int anInt : ints) {
System.out.print(anInt+" ");
}
System.out.println();
}
return dp[m][n];
}
7、53. 最大子序和
1、步骤
1、定义数组含义:dp[i] 表示 一nums[i] 字段结尾的最大子序和
2、数组元素之间的关系式:dp[i]=Max{ dp[i-1]+nums[i] ,nums[i]}
3、初始化值:dp[0]=nums[0]
2、代码实现
public int maxSubArray(int[] nums) {
int[] dp=new int[nums.length];
dp[0]=nums[0];
int max=dp[0];
for(int i=1;i<nums.length;i++){
dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);
if(dp[i]>max){
max=dp[i];
}
}
return max;
}
8、70. 爬楼梯
1、步骤
1、定义数组含义:dp[i] 表示爬到 i 阶有多少种不同的方法
2、数组元素之间的关系式:dp[i]=dp[i-1]+dp[i-2];达到第 i 阶,要么是通过爬 1 阶上来的要么是通过爬 2 阶上来的。
3、初始化值:dp[1]=1,dp[1]=2
2、代码实现
public static int climbStairs(int n) {
int[] dp=new int[n+1];
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
System.out.println();
return dp[n];
}
9、63. 不同路径 II
1、步骤
1、定义数组含义:dp[i] [j] 表示到达(i,j)一共有多少条路径
2、数组元素之间的关系式:如果在(i,j)处没有障碍,那么有 dp[i] [j] = dp[i-1] [j]+dp[i] [j-1];
3、初始化值:start位置 一直向右、一直向下走的需要初始化,但是遇到障碍物就停止初始化,后面的路没法走
2、代码实现
public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m=obstacleGrid.length;
int n=obstacleGrid[0].length;
int[][] dp=new int[m][n];
for(int i=0;i<m&&obstacleGrid[i][0]!=1;i++){
dp[i][0]=1;
}
for(int i=0;i<n&&obstacleGrid[0][i]!=1;i++){
dp[0][i]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]==0){
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}