一.坐标型动态规划
1.坐标型动态规划特点
-
坐标型动态规划可以说是最简单的动态规划类型
-
给定一个序列或网格
-
需要找到序列中某个/些子序列或网格中的某条路径
- 某种性质最大/最小
- 计数
- 存在性
-
动态规划方程f[i]中的下标i表示以ai为结尾的满足条件的子序列的性质,f[i][j]中的下标i,j表示以格子(i,j)为结尾的满足条件的路径的执行
- 最大值/最小值
- 个数
- 是否存在
2.例1
- 给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步可以向下或者向右走一步
- 网格中有些地方有障碍,机器人不能通过障碍格
- 问有多少种不同的方式走到右下角
动态规划组成部分一:确定状态
- 这题和上章某题很像,只是网格中可能有障碍
- 最后一步一定是从左边(i,j-1)或上边(i-1,j)过来,这点是没有变的,变的只是多了障碍而已
- 状态f[i][j]表示从左上角有多少种方式走到格子(i,j)(坐标型动态规划基本都是:数组下标[i][j]即坐标(i,j))
动态规划组成部分二:转移方程
- f[i][j]=f[i-1][j]+f[i][j-1]
动态规划组成部分三:初始条件和边界情况
- 初始条件和边界情况
- 如果左上角(0,0)或者右下角(m-1,n-1)格有障碍,直接输出0
- 如果(i,j)有障碍, f[i][j]=0,表示机器人不能到达此格子(0种方式)
- 初始条件: f[0][0]=1
动态规划组成部分四:计算顺序
- 计算第0行,计算第1行…
- 计算第m-1行
- 答案是f[m-1][n-1]
- 时间复杂度(计算步数):O(MN),空间复杂度(数组大小):O(MN)
代码
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
if(obstacleGrid==null||obstacleGrid.length==0){
return 0;
}
int [] [] dp = new int[obstacleGrid.length][obstacleGrid[0].length];
for(int i=0;i<obstacleGrid.length;i++){
for(int j=0;j<obstacleGrid[i].length;j++){
if(obstacleGrid[i][j]==1){
//有障碍
dp[i][j]=0;
continue;
}else{
if(i==0&&j==0) dp[i][j]=1;//没有障碍起点
else if(i==0) dp[i][j]=dp[i][j-1];//在第一行,上一题在没有障碍情况下默认是1,但是有障碍就取决于该行前面是否有障碍了
else if(j==0) dp[i][j]=dp[i-1][j];//第一列,没有障碍默认是1,有障碍就取决于该列前面是否有障碍了
else{
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
}
}
return dp[obstacleGrid.length-1][obstacleGrid[0].length-1];
}
}
3.例2(最长连续单调子序列)[Longest Increasing Continuous Subsequence]
- 题意
给定a[0],…a[n-1],找到最长的连续子序列i,i+1,i+2,…j,使得a[i]<a[i+1]<…<a[j],或者a[i]>a[i+1]]>…>a[j],输出长度j-i+1 - 例子
- 输入:[5,1,2,3,4]
- 输出:4(子序列1,2,3,4)
A.思考
- 首先,对于a[i]>a[i+1]>…>a[j],可以将整个a蓄力倒过来,就变成球最长连续上升子序列了
- 所以,只需要考虑找到最长的a[i]<a[i+1]<…<a[j]
- 可以从每个a[i]开始,一直向后延伸找到最长的连续上升序列
- 最差情况下,对于长度为N序列,需要计算O(n²)步(0,1,2,3…,n-1)
B.确定状态
- 最后一步:对于最优的策略,一定有最后一个元素a[j]
- 第一种情况:最优策略中最长连续上升子序列就是{a[j]},答案是1
- 第二种情况,子序列长度大于1,那么最优策略中a[j]前一个元素肯定是a[j-1],这种情况一定是a[j-1]<a[j],因为是最优策略,那么它选中的以a[j-1]结尾的连续上升子序列一定是最长的
- 子问题
- 要求以a[j-1]结尾的最长连续上升子序列,本来是求以a[j]结尾的最长连续上升子序列
- 化为子问题
- 状态:f[j]=以a[j]结尾的最长连续上升子序列的长度
C.转移方程
- f[j]=以a[j]结尾的最长连续上升子序列的长度
D.初始条件和边界情况
E.计算顺序
- f[j]=以a[j]结尾的最长连续上升子序列的长度
- 计算f[0],f[1],…f[n-1]
- 和硬币组合题不一样的是,最终答案并不是f[n-1]
- 因为不知道最优策略中最后一个元素是那个a[j]
- 所以答案是max{f[0]、f[1]、…f[n-1]}
- 算法时间复杂度和空间复杂度都是O(n)【如何做到空间复杂度O(1)?-覆盖,滚动数组】
F.代码
public void calc(int [] A,int n){
int [] f= new int[n];
for(int i=0;i<n;i++){
f[i]=1;
if(i>0&&A[i-1]<A[i]){
f[i]