当前位置:   article > 正文

动态规划(Dynamic-Programming)问题讲解

动态规划(Dynamic-Programming)问题讲解

动态规划类问题

  • 从已知子问题的解,推导出当前问题的解 推导过程可以表达为一个数学公式
  • 用一维或二维数组来保存之前的计算结果(可以进一步降维优化)

        将当前问题 分解成子问题 ,找出递归公式,分阶段进行求解 求解过程中缓存子问题的解,避免重复计算。

以一个简单例子Fibonacci数列为例,求Fibonacci数列第 n 项的

从第二项开始,每一项的值都等于前两项的值之和
0 1 1 2 3 5 8
dp[i] = dp[i-1] + dp[i-2]

思路:

        1.若求第 0 项或第 1 项的值,直接返回对应的值 0 或 1

        2.创建一个一维数组缓存第n项数值之前的求解结果,并初始化第一项和第二项的值

        3.使用循环计算处每一项的值,直到第 n 项,最后返回一维数组的第n项值即可

代码:

  1. public static int fibonacci(int n) {
  2. if (n == 0) {
  3. return 0;
  4. } else if (n == 1) {
  5. return 1;
  6. }
  7. int[] dp = new int[n + 1];
  8. dp[0] = 0;
  9. dp[1] = 1;
  10. for (int i = 2; i <= n; i++) {
  11. dp[i] = dp[i - 1] + dp[i - 2];
  12. }
  13. return dp[n];
  14. }

测试:

  1. public static void main(String[] args) {
  2. // 0 1 1 2 3 5 8
  3. System.out.println(fibonacciDown(13)); //求第 7 项值
  4. }

降维优化:对于每个子问题只需要三个值参与,何不用三个变量代替一维数组进行优化:

  1. /**
  2. * 求 Fibonacci 的第 n 项 (降维)
  3. *
  4. * @param n 第几项
  5. * @return
  6. */
  7. public static int fibonacciDown(int n) {
  8. if (n == 0) {
  9. return 0;
  10. } else if (n == 1) {
  11. return 1;
  12. }
  13. int a = 0, b = 1;
  14. for (int i = 2; i <= n; i++) {
  15. int c = a + b; //记录第i项的值
  16. //更新值
  17. a = b;
  18. b = c;
  19. }
  20. return b;
  21. }

Leecode62. 不同路径 题

从start走到finish有多少种走法(只能向右向下走)

 将每个格子的走法都记录出来,标识数字为 start 到该格子上的有多少走法,,找出规律

可看出规律为:dp[i][j] = dp[i][j - 1] + dp[i -1][j],并且第一行和第一列的值都为1,即走法只有一种

思路:

        1.以一个二维数组缓存每个格子的走法数

        2.遍历每行每列,求出每个格子的走法数

        3.最后返回第m行第n列的值,即为最终结果

代码:

  1. /**
  2. * 求到第m行n列有多少种走法,只能向右和向下
  3. *
  4. * @param m
  5. * @param n
  6. * @return
  7. */
  8. public static int uniquePaths2(int m, int n) {
  9. int[][] dp = new int[m][n];
  10. //初始化第一行和第一列的值为 1(其走法只有一种)
  11. for (int i = 0; i < m; i++) {
  12. dp[i][0] = 1;
  13. }
  14. for (int i = 0; i < n; i++) {
  15. dp[0][i] = 1;
  16. }
  17. for (int i = 1; i < m; i++) {
  18. for (int j = 1; j < n; j++) {
  19. dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
  20. }
  21. }
  22. print(dp);
  23. return dp[m - 1][n - 1];
  24. }

降维优化:

每次计算当前格子的走法数时,只需要上一个格子和左边格子的走法之和,何不使用一维数组,上一个格子的走法即为当前格子的做法,将dp[i][j] = dp[i][j - 1] + dp[i -1][j]改为dp[j] = dp[j] + dp[j - 1],实现降维优化的目的,以第二行到第三行为例:

代码:

  1. /**
  2. * 求到第m行n列有多少种走法,只能向右和向下 (降维,二维 变 一维)
  3. *
  4. * @param m
  5. * @param n
  6. * @return
  7. */
  8. public static int uniquePaths(int m, int n) {
  9. int[] dp = new int[n];
  10. //初始化第一行和第一列的值为 1(其走法只有一种)
  11. Arrays.fill(dp, 1);
  12. for (int i = 1; i < m; i++) {
  13. for (int j = 1; j < n; j++) {
  14. dp[j] = dp[j] + dp[j - 1]; //自己加上 上一列的
  15. }
  16. }
  17. // System.out.println(Arrays.toString(dp));
  18. return dp[n - 1];
  19. }

2. 01背包问题 - AcWing题库

从N个物体中选择物体放入体积为V的背包中,使得价值最大,每种物品只能选择一次

以一下测试示例:

  1. 4 5 //物体数量为 4,背包体积为 5
  2. 1 2 //第一个物体的体积 1 和价值 2
  3. 2 4
  4. 3 4
  5. 4 5

以一个二维数组缓存第一行只有物品A的选择,第二行只有物体A、B时的选择等..,

ABCD分别表示四个物体

二维数组 dp 中:

第一行中选择物体A,体积为1,在体积为0时不能放下为0,其它都能放下A

第二行中选择物体B,体积为2,在背包体积为0、1时不能放下,将上一行数据复制下来,背包体积为2时能放下,价值为2比上一行的A更大,为3、4、5时可以放下B此外还能放下一个物体A

第三行中选择物体C,体积为3,在背包体积为0、1、2时不能放下,将上一行数据复制下来,在背包体积 为3是虽然能放下C,但是上一行的BA价值是6,比C的价值大,所以直接复制下来,在体积为5时,当前背包除了能放下BA外还能放下一个C

第四行选择物体D,体积为4,在背包体积为0、1、2、3时不能放下,将上一行数据复制下来,在背包体积 为4是虽然能放下D,但是上一行的BA价值是6,比D的价值大,所以直接复制下来,在体积为5时同理

  1. 编号 体积(g) 价值(元) 物品名
  2. 1 1 2 A
  3. 2 2 4 B
  4. 3 3 4 C
  5. 4 4 5 D
  6. 二维数组 dp :
  7. 0 1 2 3 4 5 --> 背包体积
  8. 0 0 A A A A A A
  9. 1 0 A B BA BA BA B
  10. 2 0 A B BA BA BAC C
  11. 3 0 A B BA BA BAC D
  12. 0, 2, 2, 2, 2, 2
  13. 0, 2, 4, 6, 6, 6
  14. 0, 2, 4, 6, 6, 8
  15. 0, 2, 4, 6, 6, 8
  16. 结果:8 (BAC)

总结一个规律:

1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总体积 - 当前物品体积得到的体积值列中最大价值得物品,加到当前处
    dp[i][j] = Math.max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如dp数组中:dp[1][3] = Max(dp[0][3],B + dp[0][3-2]) = BA
2.装不下,将上一行物品复制下来
    dp[i][j] = dp[i-1][j]

代码二维:

  1. import java.util.Scanner;
  2. /**
  3. * 0 - 1背包问题
  4. */
  5. public class Main {
  6. public static void main(String[] args) {
  7. /*
  8. 编号 体积(g) 价值(元) 物品名
  9. 1 1 2 A
  10. 2 2 4 B
  11. 3 3 4 C
  12. 4 4 5 D
  13. 0 1 2 3 4 5 --> 体积
  14. 0 0 A A A A A A
  15. 1 0 A B BA BA BA B
  16. 2 0 A B BA BA BAC C
  17. 3 0 A B BA BA BAC D
  18. 0, 2, 2, 2, 2, 2
  19. 0, 2, 4, 6, 6, 6
  20. 0, 2, 4, 6, 6, 8
  21. 0, 2, 4, 6, 6, 8
  22. 结果:8 (BAC)
  23. 1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总体积 - 当前物品体积量得到得体积列中最大价值得物品,加到当前处
  24. dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
  25. 2.装不下,将上一行物品复制下来
  26. dp[i][j] = dp[i-1][j]
  27. */
  28. Scanner sc = new Scanner(System.in);
  29. int N = sc.nextInt(); //物品数量
  30. int V = sc.nextInt(); //背包容积
  31. int[] vArr = new int[N]; //N个物品的体积
  32. int[] pArr = new int[N]; //N个物品的价值
  33. for (int i = 0; i < N; i++) {
  34. vArr[i] = sc.nextInt();
  35. pArr[i] = sc.nextInt();
  36. }
  37. System.out.println(knapsackProblem01(V, vArr, pArr, N));
  38. }
  39. public static int knapsackProblem01(int V, int[] vArr, int[] pArr, int N) {
  40. int[][] dp = new int[N][V + 1];
  41. for (int j = 0; j < V + 1; j++) {
  42. if (vArr[0] <= j) { //装得下
  43. dp[0][j] = pArr[0];
  44. }
  45. }
  46. for (int i = 1; i < N; i++) {
  47. for (int j = 0; j < V+1; j++) {
  48. int x = dp[i - 1][j]; //上一行的价值
  49. if (vArr[i] <= j) { //装得下
  50. // 当前物品价值 剩余物品价值
  51. dp[i][j] = Math.max(x, pArr[i] + dp[i-1][j - vArr[i]]);
  52. } else { //装不下
  53. dp[i][j] = x;
  54. }
  55. }
  56. }
  57. return dp[N - 1][V];
  58. }
  59. }

 代码(降维成一维数组):

  1. public static void main(String[] args) {
  2. Scanner sc = new Scanner(System.in);
  3. int N = sc.nextInt(); //物品数量
  4. int V = sc.nextInt(); //背包容积
  5. int[] vArr = new int[N]; //N个物品的体积
  6. int[] pArr = new int[N]; //N个物品的价值
  7. for (int i = 0; i < N; i++) {
  8. vArr[i] = sc.nextInt();
  9. pArr[i] = sc.nextInt();
  10. }
  11. int[] dp = new int[V + 1];
  12. for (int j = 0; j < V + 1; j++) {
  13. if (vArr[0] <= j) { //装得下
  14. dp[j] = pArr[0];
  15. } else { //装不下
  16. dp[j] = 0;
  17. }
  18. }
  19. //System.out.println(Arrays.toString(dp));
  20. for (int i = 1; i < N; i++){
  21. for (int j = V; j > 0; j--) {
  22. if (vArr[i] <= j) { //装得下
  23. // pArr[i]当前物品价值 dp[j - vArr[i]]剩余空间在上次中(避免同一物品重复使用)能装的最大值
  24. dp[j] = Math.max(dp[j], pArr[i] + dp[j - vArr[i]]);
  25. }
  26. }
  27. //System.out.println(Arrays.toString(dp));
  28. }
  29. System.out.println(dp[V]);
  30. }

3. 完全背包问题 - AcWing题库

与01背包问题的区别:

dp[i][j] = Math.max(x, pArr[i] + dp[i][j - vArr[i]]); //完全背包中物品数量无限,从本次物品中找,同一物品可重复使用

dp[i][j] = Math.max(x, pArr[i] + dp[i-1][j - vArr[i]]); //01背包中物品数量只有一个,从上次物品中找,同一物品只能用一次

代码(二维):

  1. import java.util.Scanner;
  2. /**
  3. * 完全背包问题
  4. */
  5. public class Main {
  6. public static void main(String[] args) {
  7. /*
  8. 编号 体积(g) 价值(元) 物品名
  9. 1 1 2 A
  10. 2 2 4 B
  11. 3 3 4 C
  12. 4 4 5 D
  13. 完全背包:
  14. 0 1 2 3 4 5 --> 体积
  15. 0 0 A AA AAA AAAA AAAAA A
  16. 1 0 A B BA BB BBA B
  17. 2 0 A B BA BB BBA C
  18. 3 0 A B BA BB BBA D
  19. 0 2 4 6 8 10 A
  20. 0 2 4 6 8 10 B
  21. 0 2 4 6 8 10 C
  22. 0 2 4 6 8 10 D
  23. 结果:10 (BAC)
  24. 1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上(总体积 - 当前物品体积)得到的体积列中最大价值得物品,加到当前处
  25. dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
  26. 2.装不下,将上一行物品复制下来
  27. dp[i][j] = dp[i-1][j]
  28. */
  29. Scanner sc = new Scanner(System.in);
  30. int N = sc.nextInt(); //物品数量
  31. int V = sc.nextInt(); //背包容积
  32. int[] vArr = new int[N]; //N个物品的体积
  33. int[] pArr = new int[N]; //N个物品的价值
  34. for (int i = 0; i < N; i++) {
  35. vArr[i] = sc.nextInt();
  36. pArr[i] = sc.nextInt();
  37. }
  38. System.out.println(knapsackProblemComplete(V, vArr, pArr, N));
  39. }
  40. public static int knapsackProblemComplete(int V, int[] vArr, int[] pArr, int N) {
  41. int[][] dp = new int[N][V + 1];
  42. for (int j = 0; j < V + 1; j++) {
  43. if (vArr[0] <= j) { //装得下
  44. dp[0][j] = pArr[0] + dp[0][j - vArr[0]];
  45. }
  46. }
  47. for (int i = 1; i < N; i++) {
  48. for (int j = 0; j < V + 1; j++) {
  49. int x = dp[i - 1][j]; //上一行的价值
  50. if (vArr[i] <= j) { //装得下
  51. // 当前物品价值 剩余体积的物品价值(从本次中找)
  52. dp[i][j] = Math.max(x, pArr[i] + dp[i][j - vArr[i]]);
  53. } else { //装不下
  54. dp[i][j] = x;
  55. }
  56. }
  57. }
  58. return dp[N - 1][V];
  59. }
  60. }

降维:

  1. import java.util.Scanner;
  2. /**
  3. * 完全背包问题
  4. */
  5. public class Main {
  6. public static void main(String[] args) {
  7. /*
  8. 1. n个物品都是固体,有重量和价值
  9. 2. 现在你要取走不超过 10克 的物品
  10. 3. 每件物品只能使用一次
  11. 编号 体积(g) 价值(元) 物品名
  12. 1 1 2 A
  13. 2 2 4 B
  14. 3 3 4 C
  15. 4 4 5 D
  16. 完全背包:
  17. 0 1 2 3 4 5 --> 体积
  18. 0 0 A AA AAA AAAA AAAAA A
  19. 1 0 A B BA BB BBA B
  20. 2 0 A B BA BB BBA C
  21. 3 0 A B BA BB BBA D
  22. 0 2 4 6 8 10 A
  23. 0 2 4 6 8 10 B
  24. 0 2 4 6 8 10 C
  25. 0 2 4 6 8 10 D
  26. 结果:10 (BAC)
  27. 1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总重量 - 当前物品重量得到得重量列中最大价值得物品,加到当前处
  28. dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
  29. 2.装不下,将上一行物品复制下来
  30. dp[i][j] = dp[i-1][j]
  31. 4 5
  32. 1 2
  33. 2 4
  34. 3 4
  35. 4 5
  36. */
  37. Scanner sc = new Scanner(System.in);
  38. int N = sc.nextInt(); //物品数量
  39. int V = sc.nextInt(); //背包容积
  40. int[] vArr = new int[N]; //N个物品的体积
  41. int[] pArr = new int[N]; //N个物品的价值
  42. for (int i = 0; i < N; i++) {
  43. vArr[i] = sc.nextInt();
  44. pArr[i] = sc.nextInt();
  45. }
  46. System.out.println(knapsackProblemComplete2(V, vArr, pArr, N));
  47. }
  48. /**
  49. * 取total重量的物品 并且 价值最大(降维)
  50. *
  51. * @return 最大价值
  52. */
  53. public static int knapsackProblemComplete2(int V, int[] vArr, int[] pArr, int N) {
  54. int[] dp = new int[V + 1];
  55. for (int j = 0; j < V + 1; j++) {
  56. if (vArr[0] <= j) { //装得下
  57. dp[j] = pArr[0] + dp[j - vArr[0]];
  58. }
  59. }
  60. for (int i = 1; i < N; i++) {
  61. for (int j = 0; j < V + 1; j++) {
  62. int x = dp[j]; //上一行的价值
  63. if (vArr[i] <= j) { //装得下
  64. // 当前物品价值 剩余物品价值
  65. dp[j] = Math.max(x, pArr[i] + dp[j - vArr[i]]);
  66. } else { //装不下
  67. dp[j] = x;
  68. }
  69. }
  70. // System.out.println(Arrays.toString(dp));
  71. }
  72. return dp[V];
  73. }
  74. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/751459
推荐阅读
相关标签
  

闽ICP备14008679号