动态规划
1.动态规划的三大步骤定义
动态规划的作用
1.计数:a.有多少种方式走到右下角
b.有多少种选出k个数使得和是Sum
2.求最大最小值
a.从左上角走到右下角路径的最大数字和
b.最长上升子序列长度
3.求存在性
a.取石子游戏,先手是否必胜
b.能不能选出k个数使得和是Sum
(1)定义数组元素的含义,假设用一维数组dp[]保存历史数组,设置好dp[i]是代表什么意思。
(2)找出数组元素之间的关系式:就是和归纳法相似,计算dp[n],利用dp[n-1],...dp[1],来推出dp[n],找出元素之间的关系。dp[n]=dp[n-1]+dp[n-2],
(3)找出初始值。要知道初始值,dp[3]=dp[2]+dp[1]。要能直接获得dp[2]和dp[1]的初始值。
2.案例
案例1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求青蛙跳上一个n级台阶总共有多少种跳法。
(1)定义数组元素的含义:青蛙跳上n级台阶总共有多少种跳法,跳上一个i级的台阶总共有dp[i]种跳法。
(2)找出数组元素间的关系式:目的是求dp[n],规模为n,比它小的规模是n-1,n-2,n-3,就是找出他们之间的关系。dp[n]=dp[n-1]+dp[n-2].
(3)找出初始条件:n=1时,dp[1]=dp[0]+dp[-1],下标不允许为负,就相当于dp[1]=1.dp[0]=0
class Solution{
public int climbStairs(int n){
if(n==1) return 1;
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];
}
return dp[n];
}
}
案例2:一个机器人位于一个m×n网络的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角
(1)定义数组元素的含义:dp[i][j]:当机器人从左上角走到(i,j)这个位置,一共有dp[i][j]种路径。dp[m-1][n-1]就是要的答案。
(网格相当于一个二维数组,数组是从下标0开始算起的,所以右下角的位置是(m-1,n-1),所以dp[m-1][n-1]就是要的答案)
(2)找出关系数组元素间的关系式:机器人怎么才能达到(i,j)这个位置。可以向下和向右走。
一种是(i-1,j)这个位置走一步到达
一种是(i,j-1)这个位置走到下一步
因为计算所有可能的步骤,所以把所有可能走的路径都加起来,所以关系式是dp[i][j]=dp[i-1][j]+dp[i][j-1].
(3)找出初始值:假如dp[i][j]中,如果i或者j有一个为0,就不能使用关系式了。这样就把i-1和j-1变成负数了。所以计算出初始值为dp[0][0....n-1]和dp[0...m-1][0],相当于图中的最上面一行和左边一列。
dp[0][0....n-1]=1;
dp[0...m-1][0]=1;
class Solution{
public static int uniquePaths(int m,int n){
if(m<=0||n<=0){
return 0;
}
int[][] dp=new int[m][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];
}
}
案例3:给定一个包含非负整数的m×n网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。(算最优路径和)
(1)定义数组元素的含义:定义dp[i][j]的含义:当机器人从左上角走到(i,j)这个位置时,最下的路径和是dp[i][j]。dp[m-1][n-1]是我们要的答案。数组从下标为0开始算起,所以下角位置是(m-1,n-1).
(2)找出关系数组元素间的关系式
机器人可以向下或者向右走,所以有两种方式达到
一种是从(i-1,j)这个位置走一步到达
一种是从(i,j-1)这个位置走一步到达
计算哪一个路径和是最小的。
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+arr[i][j];
(3)找出初始值
dp[i][j]中,如果i或者j有一个为0,也是不能使用关系式。这个时候把i-1或者j-1,就变成负数了。初始值是计算出所有的dp[0][0...n-1]和所有的dp[0...m-1][0]。
dp[0][j]=arr[0][j]+dp[0][j-1];
dp[i][0]=arr[i][0]+dp[i][0];
class Solution{
public static int uniquePaths(int[][] arr){
int m=arr.length;
int n=arr[0].length;
if(m<=0||n<=0){
return 0;
}
int[][] dp=new int[m][n];
dp[0][0]=arr[0][0];
for(int i=1;i<m;i++){
dp[i][0]=dp[i-1][0]+arr[i][0];
}
for(int j=1;j<n;j++){
dp[0][j]=dp[0][j-1]+arr[0][j];
}
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])+arr[i][j];
}
}
return dp[m-1][n-1];
}
}
案例4:给定两个单词word1和word2,计算将word1转换成word2所使用的最少操作数
(1)定义数组元素的含义:所使用的的最少操作数,定义dp[i][j]的含义:当字符串word1的长度为i,word2的长度为j,将word1转化为word2所使用的最少操作次数为dp[i][j].
(2)找出关系数组元素间的关系式:找出dp[i][j]元素之间的关系,dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1]存在着某种关系,通过规模小的推导出规模大的。
a.word1[i]与word2[j]相等,这个时候不需要进行任何操作,显然有dp[i][j]=dp[i-1][j-1].
b.word1[i]与word2[j]不相等,就必须进行调整,调整的操作有3种,三种操作关系如下(注意字符串与字符的区别):
1)如果把字符word1[i]替换成与word2[j]相等,则有dp[i][j]=dp[i-1][j-1]+1;
2)如果在字符串word1末尾插入一个与word2[j]相等的字符,则有dp[i][j]=dp[i][j-1]+1;
3)如果把字符word1[i]删除,则有dp[i][j]=dp[i-1][j]+1;
选择使得dp[i][j]的值最小,dp[i][j]=min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1
(3)找出初始值
dp[i][j]中,如果i或者j有一个为0,不能使用关系式。会使i-1或者j-1变成负数。我们的初始值计算出所有的dp[0][0....n]和所有的dp[0...m][0],这个还是非常容易计算的,因为当有一个字符串的长度为0时,转化为另外一个字符串,就只能一直进行插入或者删除
class Solution{
public int minDistance(String word1,String word2){
int n1=word1.length();
int n2=word2.length();
int[][] dp=new int[n1+1][n2+1];
for(int j=1;j<=n2;j++) dp[0][j]=dp[0][j-1]+1;
for(int i=1;i<=n1;i++) dp[i][0]=dp[i-1][0]+1;
for(int i=1;i<=n1;i++){
for(int j=1;j<=n2;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i][j-1]),dp[i-1][j])+1;
}
}
}
return dp[n1][n2];
}
}