动态规划
动态规划–解题技巧
一、适用条件
具有重叠子问题
子问题是不独立的,并且在后续的计算中也可能会多次用到,子问题也能按照相同的方法分割为更小的子问题。
满足最优子结构
某阶段状态一旦确定,就不受这个状态以后决策的影响,即某状态以后的国产不会影响曾经的状态,仅仅于当前状态有关。
无后效性
每个子问题的决策不能对后面未解决的问题产生影响。
二、动态规划的三大步骤
第一步:定义数组元素的含义
一般情况下使用一维数组或者二维数组保存数据,需要规定该数组代表的含义,比如dp[ i ]代表什么意思。
第二步:找出数组元素之间的关系式(状态转移方程)
当我们要计算dp[ n ]时,可以利用dp[ n-1 ]、dp[ n-2 ]、… dp[ 1 ]来推出,例如dp[ n ] = dp[ n-1 ] + dp[ n-2 ],这是最难的一步也是最关键的一步。
第三步:寻找初始值
根据限制条件推导出初始条件。
三、常见的动态规划题
案例1:简单的一维DP
青蛙跳台阶:
- 定义数组元素dp[ i ]的含义:表示跳到i级台阶有dp[ i ]种跳法。
- 状态转移方程:dp[ n ] = dp[ n-1 ] + dp[ n-2 ]
- 初始条件:dp[0] = 0,dp[1] = 1,dp[2] = 2
int f(int n) {
if(n <= 2)
return n;
int[] dp = new int[n+1];
int f1 = 1, f2 = 2;
int i = 3;
while(i <= n) {
dp[i] = dp[i-1] + dp[i-2];
i++;
}
return dp[n];
}
案例2:二维数组的DP
问题描述:一个机器器人位于一个 m x n ⽹网格的左上⻆角)。机器器⼈每次只能向下或者向右移动一步。机器⼈人试图达到⽹网格的右下⻆角。 问总共有多少条不同的路径?
- 定义数组元素dp[ i ] [ j ]的含义:当机器人从左上角走到(i,j)时,一共有dp[ i ] [ j ]种路劲。
- 状态转移方程:dp[ i ] [ j ] = dp[ i-1 ] [ j ] + dp[ i ] [ j-1 ]
- 定义初始值:dp[ 0 ] [ 0,,j,,n-1 ] = 1,dp[ 0,,j,,m-1 ] [ 0 ] = 1
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];
}
四、动态规划如何优化
在使用数组时,对于不会在用到的值就不需要保存,可以减少空间复杂度。比如案例2中当我们计算第i行的值时只会用到第i-1行的值,对于第1行至i-2行的值没有用到,所以可以只使用一个一维数组来保存上一行的值,在计算的过程中,需要不断更新dp[]的值。