vlambda博客
学习文章列表

如何弄明白动态规划?

一、什么是动态规划?

动态规划就是通过采用分而治之的策略,通过解决大问题的子问题从而解决整体的做法。动态规划的核心思想就是将大问题拆分成多个子问题,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

动态规划有三个重要的概念

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];

}