聊聊笔试必刷的动态规划进阶题

开发 后端
定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小,其中 arr[m][n] 表示具体的值。每次只能向下或者向右移动一步。

[[399414]]

本文转载自微信公众号「Java极客技术」,作者鸭血粉丝 。转载本文请联系Java极客技术公众号。

Hello 大家好,我是阿粉,前面有篇文章给大家介绍了动态规划,并通过两个案例给大家演示,后台很多小伙伴也提供了很多建议,没看过的小伙伴可以去看下什么是动态规划——从青蛙跳台阶开始了解。今天再给大家介绍两个案例,帮助大家更好的掌握也顺便回顾一下。

案例 1

问:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小,其中 arr[m][n] 表示具体的值。每次只能向下或者向右移动一步。这个问题是上篇文章中的案例的进阶篇。

思考

根据上篇文章提供的思路,我们依次进行相关的步骤:

  1. 定义变量:我们定义从左上角走到(i, j) 这个位置时,最小的路径和是 dp[i - ][j - 1]。那么,dp[m-1] [n-1] 就是我们要的答案;
  2. 寻找关系:dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j]; arr[i][j] 表示网格中的数值,到达当前格子的最小路径等于左边或者上边中较小的路径加上格子本身的数值;
  3. 定义初始值:dp[i][0] = dp[i-1][0] + arr[i][0];,dp[0][i] = dp[0][i-1] + arr[0][i];;第一行或者第一列的时候就是整行和整列的数值累加。

编码

上面的分析可以想到,那么接下来我们就需要用代码来实现了,对于需要使用到之前的记录,我们可以考虑用二维数组来记录,所以就有了下面的这段代码。

  1. public int dp(int[][] arr) { 
  2.     int m = arr.length; 
  3.    int n = arr[0].length; 
  4.     if (m <=0 || n <= 0) { 
  5.         return 0; 
  6.     } 
  7.     int[][] dp = new int[m][n]; 
  8.     // 初始化 
  9.    dp[0][0] = arr[0][0]; 
  10.    // 初始化最左边的列 
  11.    for(int i = 1; i < m; i++){ 
  12.       dp[i][0] = dp[i-1][0] + arr[i][0]; 
  13.     } 
  14.    // 初始化最上边的行 
  15.    for(int i = 1; i < n; i++){ 
  16.       dp[0][i] = dp[0][i-1] + arr[0][i]; 
  17.     } 
  18.      
  19.     for (int i = 1; i < m; i++) { 
  20.         for (int j = 1; j < n; j++) { 
  21.             dp[i][j] = Math.min(dp[i-1][j],dp[i][j-1]) + arr[i][j]; 
  22.         } 
  23.     } 
  24.     return dp[m - 1][n - 1]; 

解释下上面的代码,首先我们创建了一个二维数组 dp[m][n],用于记录到达位置的最短路径,由于当前的路径是由左边或者上边的最小路径加上当前格子的数值得到。这里我们需要找到对应关系,也就是dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j],这里我们需要取相邻的最小值再加上当前格子的数值。

案例 2

问:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。Leetcode 322. 零钱兑换。

思考

  1. 定义变量:定义 dp[i] 表示凑成金额i,所需要的最少硬币个数,即 dp[amount] 则是我们需要求解的;
  2. 寻找关系:假设我们有三种硬币a,b,c,兑换的金币数为 m,我们可以推出 dp[m] = min(dp[m - a], dp[m - b], dp[m - c]) + 1,因为如果我们是需要求 m 金额的最少硬币个数,如果我们求出了 m - a 金额需要的硬币个数,在加上一个 a 就可以得到 m,硬币个数只要加 1。其实b,c 同理。并且我们需要取所有硬币种类的最小数。
  3. 定义初始值:dp[0] = 0,没有金额当时也不需要硬币个数,dp[i - coins[j] 需要有解;

编码

  1. public int dp(int[] coins, int amount) { 
  2.  
  3.         int[] dp = new int[amount + 1]; 
  4.         int size = coins.length; 
  5.         int i = 0; 
  6.         int j = 0; 
  7.      # 定义初始值 
  8.         dp[0] = 0; 
  9.         for (i = 1; i <= amount; i++) { 
  10.             #赋值,当不能组合和输出 -1 判断使用 
  11.             dp[i] = Integer.MAX_VALUE; 
  12.            # 遍历 coins 中的硬币种类 
  13.             for (j = 0; j < size; j++) { 
  14.                 if (i >= coins[j] && (dp[i - coins[j]]) != Integer.MAX_VALUE) { 
  15.                     dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]); 
  16.                 } 
  17.             } 
  18.         } 
  19.         if (dp[amount] == Integer.MAX_VALUE) { 
  20.             dp[amount] = -1 ; 
  21.         } 
  22.         return dp[amount]; 
  23.     } 

总结

 

动规划的题目在 LeetCode 上面有很多,大家可以根据上面提供的思路去多刷几道题,慢慢就会有感觉了,刷完动态规划的题目,相信对大家工作或者找工作肯定有很大的帮助。https://leetcode-cn.com/problemset/all/?topicSlugs=dynamic-programming

 

责任编辑:武晓燕 来源: Java极客技术
相关推荐

2021-12-27 08:22:18

Kafka消费模型

2023-10-27 08:59:00

网络wiresharkIO

2021-12-06 11:03:57

JVM性能调优

2021-12-09 12:22:28

MyBatis流程面试

2023-06-19 07:31:34

普通动态规划字符串

2018-03-06 11:17:27

2022-08-29 09:06:43

hippo4j动态线程池

2023-12-02 20:41:32

内存kube

2023-03-26 09:08:36

2021-01-19 08:25:20

Java反射进阶

2020-12-07 06:01:37

Css前端content

2022-10-19 08:19:32

动态基线预警

2020-06-02 07:46:30

5G动态频谱

2021-10-28 18:58:57

动态规划数据结构算法

2022-12-29 08:12:51

动态规划profit

2022-06-29 16:37:15

算法JS数组

2010-08-18 10:27:56

2010-08-23 15:11:11

2021-01-22 05:49:41

数据源思路规划

2009-12-15 13:23:21

动态路由协议
点赞
收藏

51CTO技术栈公众号