20210916 动态规划
文章目录
动态规划题目特点:
- 计数
- 如:
- 有多少种方式走到右下角
- 有多少种方式选出k个数的和是给定的sum
- 如:
- 求最大最小值
- 如:
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
- 如:
- 求存在性
- 如:
- 取石子判断是否能取胜
- 能不能选出k个数使得他们的和为sum
- 如:
动态规划题目的一般解题思路:
- 确定状态
- 研究最优策略的最后一步
- 将问题转换为更小规模的子问题
- 转移方程
- 可以根据子问题直接得到
- 初始条件和边界情况
- 需要仔细判断
- 判断计算的顺序
- 总体思路是利用已经计算过的结果得到当前规模问题的结果
常见的动态规划类型
- 坐标型动态规划
- 一维二维矩阵中的问题
- 序列型动态规划
- 如从空序列开始,硬币序列能够凑出总值的问题
- 划分型动态规划
- 把n个东西分为几段每段又满足什么条件
- 区间型动态规划
- 矩阵乘法、气球爆炸等题目,存在左右端点、区间
- 背包型动态规划
- 如何求最大价值,或者最大重量之类的问题
- 最长序列型动态规划
- 最长上升子序列之类的题目
- 博弈型动态规划
- 下棋,判断是否先手必胜之类的题目
- 综合类型
典中典的例题
1.你有三种硬币,分别面值2元、5元和7元每种硬币足够多;当前买一本书需要27元。问:如何使用最少的硬币数量组合刚好付清,且无需找零。
-
确定状态
- 最优策略的最后一步:
- 当前最后一步是:最后一枚硬币A(k)面值刚好能够付清,并且不需要找零,同时组合的硬币数量最少
- 子问题:
- 需要判断A(k-1)能否被当前硬币面值组合付清,并且不需要找零,同时组合的硬币数量最少
- 结果:当前问题规模从k 转换为 k-1
- 最优策略的最后一步:
-
转移方程
- 分析:
- 如果可以满足当前面值组合并且组合的硬币数最少,使用dp[k] 保存当前组合使用的最少硬币数
- 那么,dp[k-1] 可以满足当前面值组合并且组合的硬币数最少
- 于是得到状态转移方程:
- 最后那枚硬币为A(k),且面值仅可能为 2或5或7
- 如果硬币的面值为 2 :dp[27] = 1 + dp[25]
- 如果硬币的面值为 5 :dp[27] = 1 + dp[22]
- 如果硬币的面值为 7 :dp[27] = 1 + dp[20]
- 除此之外,没有更多的情况
- 因为要求组合的硬币数量最少:
- dp[27] = min{dp[25] + 1, dp[22] + 1, dp[20] + 1}
- 最后那枚硬币为A(k),且面值仅可能为 2或5或7
- 分析:
-
初始条件和边界情况
- 分析:
- 如果dp[x-2]或dp[x-5]或dp[x-7] 其中的 x-2、x-5、x-7 小于0怎么办,什么时候停下来
- 事先定义,如果不能拼出结果,则定义该dp为正无穷
- dp[负数] = INFINITY
- dp[1] = min{dp[-1] , dp[-4], dp[-6]} —> INFINITY
- dp[0] = 0 零元需要零块硬币解决当前问题
- 分析:
-
计算顺序
- 如果从大到小计算,需要计算子问题很多次
- 如果从小到大计算,更大规模数量的问题可以通过较小规模问题的值得到
-
Java实现代码:
输入:
2 5 7 27输出:
5代码实现
import java.util.*; public class Main{ public static void main(String[] args){ //输入处理 Scanner sc = new Scanner(System.in); String line = sc.nextLine(); int target = Integer.parseInt(sc.nextLine()); String[] str = line.split(" "); int[] coinValue = new int[str.length]; for(int i = 0; i < str.length; i++){ coinValue[i] = Integer.parseInt(str[i]); } System.out.println(findMinNum(coinValue,target)); } public static findMinNum(int[] group, int target){ int n = group.length; int[] dp = new int[n + 1]; //边界条件 dp[0] = 0; //初始化 for(int i = 1; i <= target; i ++){ //从1开始,所以要取到target dp[i] = Integer.MAX_VALUE; for(int j = 0; j < n; j++){ dp[i] = Math.min(dp[i],dp[i - group[j]] + 1); } } if(dp[target] == Integer.MAX_VALUE){ return -1; } else{ return dp[target]; } } }
2.给定m*n的网格,有个机器人从左上角[0,0]出发,每一步可以向下或者向右走一步。问有多少种方式能走到最右下角
-
确定状态
- 最优策略的最后一步
- 最后一步:当前最后到[m-1,n-1],可以从上面的格子[m-2,n-1],和左边的格子[m-1,n-2]两种方式
- 子问题
- 分析:
- 如果当前有X种方式从上面格子[m-2,n-1]走下来,有Y种方式从左边格子[m-1,n-2]走到右边
- 所以走到[m-1,n-1]存在X+Y种方式
- 子问题:有多少种方式走到[m-2,n-1],以及有多少种方式走到[m-1,n-2]
- 分析:
- 最优策略的最后一步
-
转移方程
- f[i][j] = f[i - 1][j] + f[i][j - 1]
-
初始条件和边界条件
- 初始条件f[0][0] = 1 —> 机器人有一种方式走到f[0][0]
- 边界情况:f[i][j] 当 i == 0 或 j == 0则只有一种方式,即从上往下或者从左往右走
-
计算顺序
- 从上到下,从左到右
- 利用前面已计算过的结果
-
Java实现代码:
输入
1 1输出
1代码实现:
import java.util.*; public class Main{ public static void main(String[] args){ Scanner sc = new Scanner(System.in); int m = Integer.parseInt(sc.nextInt()); int n = Integer.parseInt(sc.nextInt()); System.out.println(findWays(m,n)); } public static int findWays(int m, int n){ int[][] dp = new int[m][n]; // 从上到下 for(int i = 0; i < m; i++){ //从左到右 for(int j = 0; j < n; j++){ if(i == 0 || j == 0){ dp[i][j] = 1; break; } dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[m - 1][n - 1]; } }
本文深入探讨了动态规划的概念,包括其特点、解题思路和常见类型。通过两个经典实例——硬币组合最少数量问题和网格行走路径计数,详细阐述了动态规划的求解过程,包括状态定义、转移方程、初始条件和计算顺序。同时提供了Java代码实现,帮助读者更好地理解和应用动态规划。
577

被折叠的 条评论
为什么被折叠?



