Dynamic Programming --- 动态规划刷题(一)

x33g5p2x  于2022-05-05 转载在 其他  
字(4.0k)|赞(0)|评价(0)|浏览(565)

动态规划

通俗的来说,动态规划就是把大事情化成小事情,小事情变无的思路,

动态规划的三特点

  1. 把原来的问题分解成了几个相似的子问题。
  2. 所有的子问题都只需要解决一次。
  3. 储存子问题的解。

动态规划的四要素

  1. 状态定义
  2. 状态间的转移方程定义
  3. 状态的初始化
  4. 返回结果

第一题 : 零钱兑换

Leet Code 322: 零钱兑换
描述:
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
你可以认为每种硬币的数量是无限的

解题思路:

  1. 状态 F(i) : 表示凑成i所需最少的硬币个数
  2. 状态转移方程: F(i) = F(coins[j]) + min(F(i-coins[j]))(coins[j[表示已有的硬币)
  3. 初始状态: F(0) = 0; F(coins[j]) = 1 (已有的面值可以直接拿,所以是1)
  4. 返回结果: F(M) (M表示amount)

代码实现:

  1. class Solution {
  2. public int coinChange(int[] coins, int amount) {
  3. // 创建一个大小为 amount+1 的数组dp
  4. int[] dp = new int[amount+1];
  5. // 将数组初始化为 -1
  6. Arrays.fill(dp,-1);
  7. // 将金额零的最小硬币个数改为0 即 dp[0] = 0
  8. dp[0] = 0;
  9. // 遍历dp数组,从1下标开始,依次计算金额的最少硬币个数
  10. for(int i = 1;i <= amount;i++) {
  11. // 遍历coins数组
  12. for(int j = 0;j < coins.length;j++){
  13. // 面额>=硬币面值 当dp[i-coins[j]] == -1时,表示没有与之对应的硬币,也就不能组成总金额
  14. if(coins[j] <= i && dp[i-coins[j]] != -1){
  15. // dp[i] == -1时,表示此时还未计算 或 dp[i] > 真正在计算的最少硬币个数 则需要更新dp[i]
  16. if(dp[i] == -1 || dp[i] > dp[i-coins[j]]+1){
  17. dp[i] = dp[i-coins[j]]+1;
  18. }
  19. }
  20. }
  21. }
  22. return dp[amount];
  23. }
  24. }

第二题: 跳石板

来源:牛客网 跳石板
描述:
小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24: 4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板

解题思路:

  1. 状态 F(i) : 表示到达i的最小步数
  2. 状态转移方程: F(i+j) = min(F(i)+1,F(i+j)) (i表示所在位置,j表示可以跳的步数)
  3. 初始状态: F(N) = 0; (N表示起始位置)
  4. 返回结果: F(M) (M表示需要到达的石板)

代码实现:

  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.Scanner;
  4. public class Main {
  5. // 用ArrayList 存放 初本身和i的公约数
  6. public static ArrayList<Integer> maxDivisor(int n ){
  7. ArrayList<Integer> arrayList = new ArrayList<>();
  8. // 这里使用Math.sqrt是为了防止超时
  9. for (int i = 2; i <= Math.sqrt(n); i++) {
  10. if(n % i == 0) {
  11. arrayList.add(i);
  12. // 当n/i!=i时,需要添加到arraylist中,否则会漏解
  13. if (n / i != i) {
  14. arrayList.add(n / i);
  15. }
  16. }
  17. }
  18. return arrayList;
  19. }
  20. public static void main(String[] args){
  21. Scanner sc = new Scanner(System.in);
  22. int n = sc.nextInt();
  23. int m = sc.nextInt();
  24. int[] dp = new int[m+1];
  25. // 将dp数组初始化为-1
  26. Arrays.fill(dp,-1);
  27. // dp[n]的初始状态为0
  28. dp[n] = 0;
  29. for(int i = n;i < m; i++){
  30. // 如果dp[i]=-1 表示当前的位置到不了,所以就不需要往下走了
  31. if(dp[i] == -1) continue;
  32. ArrayList<Integer> arrayList = maxDivisor(i);
  33. // 遍历所有的因子
  34. for(int j : arrayList){
  35. // i+j<=m 防止数组越界
  36. // 因为初始化是-1 求最小值min 就需要判断
  37. if(i + j <= m && dp[i+j] != -1){
  38. dp[i+j] = Math.min(dp[i]+1,dp[i+j]);
  39. }else if(i + j <= m){
  40. dp[i+j] = dp[i] + 1;
  41. }
  42. }
  43. }
  44. //遍历结束就返回dp[m]
  45. // 如果里面是-1就表示到达不了
  46. System.out.println(dp[m]);
  47. }
  48. }

第三题: 不同路径

LeetCode 62: 不同路径
描述:
一个机器人位于一个 m x n 网格的左上角(起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?

解题思路:

  1. 状态 F(i,j) : 表示到达(i,j)下标可以走的路径
  2. 状态转移方程:
    ① i == 0 || j == 0, F(i,j) == 1
    ② F(i,j) = F(i-1,j) + F(i,j-1)
  3. 初始状态: F(0,0) = 1;
  4. 返回结果: F(m-1,n-1)

代码实现:

  1. class Solution {
  2. public int uniquePaths(int m, int n) {
  3. int[][] dp = new int[m][n];
  4. dp[0][0] = 1;
  5. for(int i = 0;i < m;i++){
  6. for(int j = 0; j < n;j++){
  7. if(j==0 || i==0){
  8. dp[i][j] = 1;
  9. }else{
  10. dp[i][j] = dp[i-1][j]+dp[i][j-1];
  11. }
  12. }
  13. }
  14. return dp[m-1][n-1];
  15. }
  16. }

第四题: 最大子数组和

LeetCode 53: 最大子数组和
描述:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。

解题思路:

  1. 状态F(i): 表示第i个和最大的值
  2. 状态转移方程: F(i) = max(F(i),F(i-1)+F(i))
  3. 初始状态:F[0] = nums[0];
  4. 返回值: 返回max(F(i));

代码实现:

  1. class Solution {
  2. public int maxSubArray(int[] nums) {
  3. // 状态F(i): 表示第i个和最大的值
  4. // 状态转移方程: F(i) = max(F(i),F(i-1)+F(i))
  5. // 初始状态:F[0] = nums[0];
  6. // 返回值: 返回max(F(i));
  7. int[] dp = new int[nums.length];
  8. dp[0] = nums[0];
  9. int max = dp[0];
  10. for(int i = 1; i < nums.length; i++){
  11. dp[i] = Math.max(nums[i],dp[i-1] + nums[i]);
  12. max = Math.max(dp[i],max);
  13. }
  14. return max;
  15. }
  16. }

第五题: 最小路径和

LeetCode 64: 最小路径和
描述:
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。

解题思路:

  1. 状态F(i,j): 到达i,j的最小路径和
  2. 状态转移方程: F(i,j) = min(F(i,j-1),F(i-1,j))+F(i,j)
    i == 0 => F(i,j) = F(i,j-1) + F(i,j)
    j == 0 => F(i,j) = F(i-1,j) + F(i,j)
  3. 初始状态: F(0,0) = gird[0][0]
  4. 返回值: F(grid.length-1,grid[0].length-1)的值就是返回值

代码实现:

  1. class Solution {
  2. public int minPathSum(int[][] grid) {
  3. // 状态F(i,j): 到达i,j的最小路径和
  4. // 状态转移方程: F(i,j) = min(F(i,j-1),F(i-1,j))+F(i,j)
  5. // i == 0 => F(i,j) = F(i,j-1) + F(i,j)
  6. // j == 0 => F(i,j) = F(i-1,j) + F(i,j)
  7. // 初始状态: F(0,0) = gird[0][0]
  8. // 返回值: F(grid.length-1,grid[0].length-1)的值就是返回值
  9. for(int i = 0; i < grid.length; i++){
  10. for(int j = 0; j < grid[i].length;j++){
  11. if(i == 0 && j == 0) continue;
  12. if(i == 0) grid[i][j] = grid[i][j-1]+grid[i][j];
  13. else if(j == 0) grid[i][j] = grid[i-1][j]+grid[i][j];
  14. else grid[i][j] = Math.min(grid[i-1][j],grid[i][j-1])+grid[i][j];
  15. }
  16. }
  17. return grid[grid.length-1][grid[0].length-1];
  18. }
  19. }

相关文章