优化路线:

try(递归尝试)-> 记忆化搜索(dp)-> 严格表结构(dp)-> 严格表结构高精(dp)
try(递归尝试):会产生重复计算,浪费时间
记忆化搜索:用一个缓存来缓存之前的数据,避免重复计算。不整理位置之间的依赖关系。
严格表结构:纠结位置之间的依赖顺序。
做动态规划的题目,首先进行递归尝试,然后优化成记忆化搜索,最后根据位置的依赖关系,转换成严格表结构,直接将递归尝试的代码,复制然后进行边界修改,不需要的进行删除。
严格表结构高精:在严格表结构的基础上进行观察产生的

决定暴力尝试好坏的因素有哪些?

  1. 单个可变参数的维度,最好是一个整数,最好不要用一个数组来表示不确定的状态。
  2. 可变参数的个数少的好。一维就是一行值,二维是一个二维表,三维就是一个立体。

    阿里笔试题:机器人走K步到达M点问题

  3. 假设有排成一行的N个位置,记为1~N,N一定大于或等于2;

  4. 开始时机器人在M(1 <= M <= N)位置上,
  5. 如果机器人位于1位置,那么下一步只能走到2位置,
  6. 如果机器人位于N位置,那么下一步只能走到N-1位置,
  7. 如果机器人位于中间的任一位子,那么下一步可以向左走,也可以向右走。
  8. 机器人必须走K步,最终来到P(1 <= P <= N),给定参数N,M,K,P,有多少种走法?

    1. public class RobotWalk {
    2. public static int ways1(int N, int M, int K, int P) {
    3. // 参数无效直接返回0
    4. if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
    5. return 0;
    6. }
    7. // 总共N个位置,从M点出发,还剩K步,返回最终能达到P的方法数
    8. return walk(N, M, K, P);
    9. }
    10. // 递归版本
    11. // N : 位置为1 ~ N,固定参数
    12. // cur : 当前在cur位置,可变参数
    13. // rest : 还剩res步没有走,可变参数
    14. // P : 最终目标位置是P,固定参数
    15. // 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,
    16. // 停在P位置的方法数作为返回值返回
    17. // 这种方式会产生重复计算,浪费时间
    18. // f(N,4,2,P) --> f(N,3,1,P)、f(N,3,3,P)
    19. // f(N,3,1,P) --> f(N,2,2,P)
    20. // f(N,3,3,P) --> f(N,2,2,P)、f(N,2,4,P)
    21. public static int walk(int N, int cur, int rest, int P) {
    22. // 如果没有剩余步数了,当前的cur位置就是最后的位置
    23. // 如果最后的位置停在P上,那么之前做的移动是有效的
    24. // 如果最后的位置没在P上,那么之前做的移动是无效的
    25. if (rest == 0) {
    26. return cur == P ? 1 : 0;
    27. }
    28. // 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2
    29. // 后续的过程就是,来到2位置上,还剩rest-1步要走
    30. if (cur == 1) {
    31. return walk(N, 2, rest - 1, P);
    32. }
    33. // 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
    34. // 后续的过程就是,来到N-1位置上,还剩rest-1步要走
    35. if (cur == N) {
    36. return walk(N, N - 1, rest - 1, P);
    37. }
    38. // 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右
    39. // 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走
    40. // 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走
    41. // 走向左、走向右是截然不同的方法,所以总方法数要都算上
    42. return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
    43. }
    44. // 对上面方式进行记忆化搜索
    45. public static int ways1(int N, int M, int K, int P) {
    46. if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
    47. return 0;
    48. }
    49. int[][] dp = new int[K + 1][N + 1];
    50. for (int i = 0; i <= K; i++) {
    51. for (int j = 0; j <= N; j++) {
    52. dp[i][j] = -1;
    53. }
    54. }
    55. return walk(N, M, K, P,dp);
    56. }
    57. public static int walk(int N, int cur, int rest, int P,int[][] dp) {
    58. if (dp[rest][cur] != -1) {
    59. return dp[rest][cur];
    60. }
    61. if (rest == 0) {
    62. dp[rest][cur] = cur == P ? 1 : 0;
    63. return dp[rest][cur];
    64. }
    65. if (cur == 1) {
    66. dp[rest][cur] = walk(N, 2, rest - 1, P,dp);
    67. } else if (cur == N) {
    68. dp[rest][cur] = walk(N, N - 1, rest - 1, P,dp);
    69. } else {
    70. dp[rest][cur] = walk(N, cur + 1, rest - 1, P,dp) +
    71. walk(N, cur - 1, rest - 1, P,dp);
    72. }
    73. return dp[rest][cur];
    74. }
    75. public static int ways2(int N, int M, int K, int P) {
    76. // 参数无效直接返回0
    77. if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
    78. return 0;
    79. }
    80. int[][] dp = new int[K + 1][N + 1];
    81. dp[0][P] = 1;
    82. for (int i = 1; i <= K; i++) {
    83. for (int j = 1; j <= N; j++) {
    84. if (j == 1) {
    85. dp[i][j] = dp[i - 1][2];
    86. } else if (j == N) {
    87. dp[i][j] = dp[i - 1][N - 1];
    88. } else {
    89. dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
    90. }
    91. }
    92. }
    93. return dp[K][M];
    94. }
    95. public static int ways3(int N, int M, int K, int P) {
    96. // 参数无效直接返回0
    97. if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
    98. return 0;
    99. }
    100. int[] dp = new int[N + 1];
    101. dp[P] = 1;
    102. for (int i = 1; i <= K; i++) {
    103. int leftUp = dp[1];// 左上角的值
    104. for (int j = 1; j <= N; j++) {
    105. int tmp = dp[j];
    106. if (j == 1) {
    107. dp[j] = dp[j + 1];
    108. } else if (j == N) {
    109. dp[j] = leftUp;
    110. } else {
    111. dp[j] = leftUp + dp[j + 1];
    112. }
    113. leftUp = tmp;
    114. }
    115. }
    116. return dp[M];
    117. }
    118. public static void main(String[] args) {
    119. System.out.println(ways1(7, 4, 9, 5));
    120. System.out.println(ways2(7, 4, 9, 5));
    121. System.out.println(ways3(7, 4, 9, 5));
    122. }
    123. }

    给一个int[]{2,7,3,5,3}正数,里面的值代表一枚硬币的面值,一个数就代表一枚硬币,给你一个数,假如是10,用最少的硬币组成10这个数

    从左往右模型,从左往右进行遍历尝试

    1. // 暴力尝试
    2. public static int minCoins(int[] arr, int aim) {
    3. return process(arr, 0, aim);
    4. }
    5. // arr[index...]组成出rest这么多钱,最少的硬币数量返回
    6. public static int process(int[] arr,int index, int rest) {
    7. if (rest < 0) {
    8. return -1;
    9. }
    10. if (rest == 0) {
    11. return 0;
    12. }
    13. // rest > 0
    14. if (index == arr.length) {
    15. return -1;
    16. }
    17. // rest > 0 并且 也有硬币
    18. int p1 = process(arr, index + 1, rest);
    19. int p2Next = process(arr, index + 1, rest - arr[index]);
    20. if (p1 == -1 && p2Next == -1) {
    21. return -1;
    22. } else {
    23. if (p1 == -1) {
    24. return p2Next + 1;
    25. }
    26. if (p2Next == -1) {
    27. return p1;
    28. }
    29. return Math.min(p1, p2Next + 1);
    30. }
    31. }
    32. // 记忆化搜索
    33. public static int minCoins(int[] arr, int aim) {
    34. int[][] dp = new int[arr.length + 1][aim + 1];
    35. for (int i = 0; i <= arr.length; i++) {
    36. for (int j = 0; j <=aim; j++) {
    37. dp[i][j] = -2;
    38. }
    39. }
    40. return process(arr, 0, aim, dp);
    41. }
    42. // arr[index...]组成出rest这么多钱,最少的硬币数量返回
    43. public static int process(int[] arr,int index, int rest, int[][] dp) {
    44. if (rest < 0) {
    45. return -1;
    46. }
    47. if (dp[index][rest] != -2) {
    48. return dp[index][rest];
    49. }
    50. if (rest == 0) {
    51. dp[index][rest] = 0;
    52. } else if (index == arr.length) {
    53. dp[index][rest] = -1;
    54. } else {
    55. int p1 = process(arr, index + 1, rest,dp);
    56. int p2Next = process(arr, index + 1, rest - arr[index],dp);
    57. if (p1 == -1 && p2Next == -1) {
    58. dp[index][rest] = -1;
    59. } else {
    60. if (p1 == -1) {
    61. dp[index][rest] = p2Next + 1;
    62. } else if (p2Next == -1) {
    63. dp[index][rest] = p1;
    64. } else {
    65. dp[index][rest] = Math.min(p1, p2Next + 1);
    66. }
    67. }
    68. }
    69. return dp[index][rest];
    70. }
    71. // 严格表结构
    72. public static int minCoins(int[] arr, int aim) {
    73. int N = arr.length;
    74. int[][] dp = new int[N + 1][aim + 1];
    75. for (int row = 0; row <= N; row++) {
    76. dp[row][0] = 0;
    77. }
    78. for (int col = 1; col <= aim; col++) {
    79. dp[N][col] = -1;
    80. }
    81. for (int index = N - 1; index >= 0; index--) {
    82. for (int rest = 1; rest <= aim; rest++) {
    83. int p1 = dp[index + 1][rest];
    84. int p2Next = -1;
    85. if (rest - arr[index] >= 0) {
    86. p2Next = dp[index + 1][rest - arr[index]];
    87. }
    88. // rest > 0 并且 也有硬币
    89. if (p1 == -1 && p2Next == -1) {
    90. dp[index][rest] = -1;
    91. } else {
    92. if (p1 == -1) {
    93. dp[index][rest] = p2Next + 1;
    94. }
    95. if (p2Next == -1) {
    96. dp[index][rest] = p1;
    97. }
    98. dp[index][rest] = Math.min(p1, p2Next + 1);
    99. }
    100. }
    101. }
    102. return dp[0][aim];
    103. }
    1. // 无效解不能返回0,返回0算是有解,解就0个硬币
    2. public class CoinsMin {
    3. public static int minCoins1(int[] arr, int aim) {
    4. if (arr == null || arr.length == 0 || aim < 0) {
    5. return -1;
    6. }
    7. return process(arr, 0, aim);
    8. }
    9. // 当前考虑的面值是arr[i],还剩rest的钱需要找零
    10. // 如果返回-1说明自由使用arr[i..N-1]面值的情况下,无论如何也无法找零rest
    11. // 如果返回不是-1,代表自由使用arr[i..N-1]面值的情况下,找零rest需要的最少张数
    12. public static int process(int[] arr, int i, int rest) {
    13. // base case:
    14. // 已经没有面值能够考虑了
    15. // 如果此时剩余的钱为0,返回0张
    16. // 如果此时剩余的钱不是0,返回-1
    17. if (i == arr.length) {
    18. return rest == 0 ? 0 : -1;
    19. }
    20. // 最少张数,初始时为-1,因为还没找到有效解
    21. int res = -1;
    22. // 依次尝试使用当前面值(arr[i])0张、1张、k张,但不能超过rest
    23. for (int k = 0; k * arr[i] <= rest; k++) {
    24. // 使用了k张arr[i],剩下的钱为rest - k * arr[i]
    25. // 交给剩下的面值去搞定(arr[i+1..N-1])
    26. int next = process(arr, i + 1, rest - k * arr[i]);
    27. if (next != -1) { // 说明这个后续过程有效
    28. res = res == -1 ? next + k : Math.min(res, next + k);
    29. }
    30. }
    31. return res;
    32. }
    33. public static int minCoins2(int[] arr, int aim) {
    34. if (arr == null || arr.length == 0 || aim < 0) {
    35. return -1;
    36. }
    37. int N = arr.length;
    38. int[][] dp = new int[N + 1][aim + 1];
    39. // 设置最后一排的值,除了dp[N][0]为0之外,其他都是-1
    40. for (int col = 1; col <= aim; col++) {
    41. dp[N][col] = -1;
    42. }
    43. for (int i = N - 1; i >= 0; i--) { // 从底往上计算每一行
    44. for (int rest = 0; rest <= aim; rest++) { // 每一行都从左往右
    45. dp[i][rest] = -1; // 初始时先设置dp[i][rest]的值无效
    46. if (dp[i + 1][rest] != -1) { // 下面的值如果有效
    47. dp[i][rest] = dp[i + 1][rest]; // dp[i][rest]的值先设置成下面的值
    48. }
    49. // 左边的位置不越界并且有效
    50. if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) {
    51. if (dp[i][rest] == -1) { // 如果之前下面的值无效
    52. dp[i][rest] = dp[i][rest - arr[i]] + 1;
    53. } else { // 说明下面和左边的值都有效,取最小的
    54. dp[i][rest] = Math.min(dp[i][rest],
    55. dp[i][rest - arr[i]] + 1);
    56. }
    57. }
    58. }
    59. }
    60. return dp[0][aim];
    61. }
    62. // for test
    63. public static int[] generateRandomArray(int len, int max) {
    64. int[] arr = new int[(int) (Math.random() * len) + 1];
    65. for (int i = 0; i < arr.length; i++) {
    66. arr[i] = (int) (Math.random() * max) + 1;
    67. }
    68. return arr;
    69. }
    70. public static void main(String[] args) {
    71. int len = 10;
    72. int max = 10;
    73. int testTime = 10000;
    74. for (int i = 0; i < testTime; i++) {
    75. int[] arr = generateRandomArray(len, max);
    76. int aim = (int) (Math.random() * 3 * max) + max;
    77. if (minCoins1(arr, aim) != minCoins2(arr, aim)) {
    78. System.out.println("ooops!");
    79. break;
    80. }
    81. }
    82. }
    83. }