如何弄明白动态规划?
一、什么是动态规划?
动态规划就是通过采用分而治之的策略,通过解决大问题的子问题从而解决整体的做法。动态规划的核心思想就是将大问题拆分成多个子问题,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
动态规划有三个重要的概念
1)最优子结构
2)边界
3)状态转移公式
二、动态规划应用
单看定义的话可能有些抽象,现在我举个动态规划实际应用的简单例子,也是面试中经常出现的问题-上台阶问题。
1)上台阶问题
如果有一座高度为十的台阶,每步只能向上一层或者两层,请问一共有多少种走法?(比如可以每次都上一层,那走法就是1,1,1,1,1,1,1,1,1,1,也可以每次上两层,那走法就是2,2,2,2,2)
如果不了解动态规划的话,可能直接选择排列组合的方式,通过遍历所有可能来获取结果。但是这种方法无论是时间复杂度还是空间复杂度都是相当的高。
现在我们可以用动态规划的思想来看下如何解决这道问题
首先我们把大问题尽量拆分成子问题,如果还有一步就能到达第十层台阶,会有几种走法?
答案是两种,一种是当前在第九层台阶,下一步只需要上一层就可以达到终点。另一种是当前在第八层台阶上,下一步只需要上两层就可以达到终点。
如果到达第八层的所有走法为F(8),到达第九层的所有走法为F(9),那么到达第十层的所有做法是否就是F(10)?
因此我们可以知道F(10)=F(8)+F(9),这样我们就完成了第一个字问题的拆分!(最优子结构)
同理可知F(9)=F(7)+F(8),F(8)=F(6)+F(7)。。。。。F(3)=F(1)+F(2)
可以得出F(n)=F(n-1)+F(n-2)(状态转移公式)
F(1)和F(2)的值我们可以直接得出结果,分别是1和2,这种无需简化直接可以得到结果的子问题,我们称之为边界。(如果问题没有边界的话,我们将永远无法得到最终结果!)
我们可以根据以上结果得出以下一段代码!
public int countClimbStairs(int n) {
if (n == 1) {
return 1;
}
if (n == 2) {
return 2;
}
int prePreCount = 1;
int preCount = 2;
int count = 0;
for (int i = 3; i <= n; i++) {
count = prePreCount + preCount;
prePreCount = preCount;
preCount = count;
}
return count;
}
2)路径问题
一个矩阵map中每个格子有一个值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,修改所有的路径中最小的路径和。
按照动态规划的思路可以将这个问题拆分,走到x,y的最小路径就是S[x][y]=map[x][y]+min(S[x-1][y](x>0), S[x][y-1](y>0))(状态转移公式)
第一行就只能一直向右和第一列只能一直向下,这个就是边界。
我们可以根据以上结果得出以下一段代码!
public int getMinRoute(int[][] map, int x, int y) {
int[][] route = new int[x][y];
int xSum = 0;
//边界赋值
for (int i = 0; i < x; i++) {
for (int j = 0; j <= i; j++) {
route[i][0] += map[j][0];
}
}
for (int i = 0; i < y; i++) {
for (int j = 0; j <= i; j++) {
route[0][i] += map[0][j];
}
}
for (int i = 1; i < x; i++) {
for (int j = 1; j < y; j++) {
route[i][j] = map[i][j] + Math.min(route[i][j - 1], route[i - 1][j]);
}
}
return route[x - 1][y - 1];
}
3)背包问题
在N件物品取出若干件放在容量为V的背包里,求背包能够容纳的最大价值。
按照同样的思路分析问题,我们可以选择拿和不拿两种,那么我们需要判断这两种情况哪种收益大,取最大值。
重量weight[n],价值value[n]
F(N,V)=max(F(N- 1, V - weight[i])+value[i], F(N - 1, V))
public static int maxValue(int[] weight, int[] value, int N, int V) {
int[][] dp = new int[N + 1][V + 1];
for (int i = 1; i < N + 1; i++) {
for (int j = 1; j < V + 1; j++) {
if (weight[i - 1] > j) {// 可能存在装不下的情况
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
return dp[N][V];
}