初识动态规划(一)简单入门动态规划与上手操作

dp动态规划

一、认识动态规划

前言:近期我在慢慢刷动态规划的题,虽然还是入门阶段,但还是准备记录我动态规划前期是如何刷题过程

先根据一个例题来引入动态规划——换零钱

  1. 提出问题:要求使用1,5,11的钞票面额进行换总金额为w的最小张数
  2. 分析问题:在兑换的最终情况下(即换一最后一张面额的情况),要么是面额为1,要么5,要么11,所以

我们可以得出:(f(w)表示换w金额需要的最少张数)

  • f(w) = f(w - 1) + 1; //该式子表示的是如果要得出f(w),就用前面已得出的f(w - 1) 加上一张面额为1的钞票即可
  • f(w) = f(w - 5) + 1;//该式子表示的是如果要得出f(w),就用前面已得出的f(w - 5) 加上一张面额为5的钞票即可
  • f(w) = f(w - 11) + 1;

故我们可知,若是求最少换零钱的张数就是

​ f(w - 1) & f(w - 5) & f(w - 11)

​ 三个中最小的那一个

​ 3. 给出具体例子

那么,假如w=15的时候,同样,钞票面额分别为1511,我们该取那种钞票呢?当然是各种方案中,cost值最低的那一个!

  -1:cost = f(14) + 1 = 4 + 1 = 5;
  -5:cost = f(10) + 1 = 2 + 1 = 3;
  -11:cost = f(4) + 1 = 4 + 1 = 5;
  再次深度解释一下:
  其中第二个取5的式子:f(10) = f(10 - 5) + 1 = 1 + 1; 即子问题的解;第三个取11的式子:f(4) = f(4 - 1) + 1 = 4; 
  1. 得出结论

    大致我们可以得出,我们所求的就是前面 当前最优值 = 前面最优值 + 1

    我们将求解f(w)就可以理解为求解多个子问题来达到求解f(w)

    这就是dp --> 动态规划

  2. 解决问题的特点
    1. 能将问题拆成几个问题,且满足无后效性(即不管前面的过程,只需要得出前面的子最优解解即可)

    2. 最优子结构性质,能够由子问题推导出结果

    3. 详细来说:

      • 求最大最小值
        • 从左上角走到右下角路径的最大数字和
        • 最长上升子序列
        • 最长等差序列
        • 最少换钱张数
      • 计数
        • 有多少种方式走到右下角
        • 有多少种方法选出k个数使得和是sum
        • 爬楼梯
      • 判断
        • 是否存在先手赢,取石子游戏
  3. 解题一般步骤
    1. 判断是否是最优问题,并存在子问题
    2. 确定dp数组用一维或者更多,并且表示什么意思
    3. 比如dp[i][j】,表示为从i下标到j下标长度的回文子串个数
    4. 推导出dp状态方程,和判断条件
    5. 确定好dp初始数组长度,和是否设立初始值
    6. 选用合适的循环条件达到自己的目的
二、下面进行具体刷动规的题型
  1. 斐波那契数

    斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

    F(0) = 0,F(1) = 1
    F(n) = F(n - 1) + F(n - 2),其中 n > 1
    给定 n ,请计算 F(n) 。

    示例 1:

    输入:n = 2
    输出:1
    解释:F(2) = F(1) + F(0) = 1 + 0 = 1
    示例 2:

    输入:n = 3
    输出:2
    解释:F(3) = F(2) + F(1) = 1 + 1 = 2
    示例 3:

    输入:n = 4
    输出:3
    解释:F(4) = F(3) + F(2) = 2 + 1 = 3

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/fibonacci-number
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    class Solution {
        public int fib(int n) {
            //动态规划 dp[i] 表示的是第i个数表示的值
            if (n == 0) return 0;	//处理特殊情况
            int []dp = new int[n + 1];	//数组长度初始化,一般情况下多一点
            dp[0] = 0;	//初始化
            dp[1] = 1;
            for (int i = 2; i <= n; i++){	//循环过程
                dp[i] = dp[i - 1] + dp[i - 2];	//状态方程
            }
            return dp[n];	//得到最终结果
        }
    }
    
  2. 零钱兑换

    给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

    计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

    你可以认为每种硬币的数量是无限的。

    示例 1:

    输入:coins = [1, 2, 5], amount = 11
    输出:3
    解释:11 = 5 + 5 + 1
    示例 2:

    输入:coins = [2], amount = 3
    输出:-1
    示例 3:

    输入:coins = [1], amount = 0
    输出:0

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/coin-change
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    class Solution {
        public int coinChange(int[] coins, int amount) {
            //动态规划
            int[] dp = new int[amount+1];
            Arrays.fill(dp, amount+1);
            //base case
            dp[0] = 0;
            // 外层的for循环在遍历时实现遍历所有状态的所有取值
            for(int i = 0; i < dp.length; i++){
                // 内层for循环求在所有选择中的最小值
                for(int coin : coins){
                    //子问题无解,跳过
                    if(i - coin < 0){
                        continue;
                    }
                    //状态转移
                    dp[i] = Math.min(dp[i],1+dp[i-coin]);
                }
            }
            //查看金额能不能算出来
            return (dp[amount] == amount+1 ? -1 : dp[amount]);
    
            //暴力递归——超时
            // if (amount == 0){
            //     return 0;
            // }
            // if (amount < 0){
            //     return -1;
            // }
            // int min = Integer.MAX_VALUE;
            // for (int i = 0; i < coins.length; i++){
            //     int temp = coinChange(coins, amount - coins[i]);
                
            //     if (temp == -1){continue;}
    
            //     min = Math.min(min, temp + 1);
            // }
            // return min == Integer.MAX_VALUE ? -1 : min;
        }
    }
    
  3. 最长递增子序列

    给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

    子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

    示例 1:

    输入:nums = [10,9,2,5,3,7,101,18]
    输出:4
    解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
    示例 2:

    输入:nums = [0,1,0,3,2,3]
    输出:4
    示例 3:

    输入:nums = [7,7,7,7,7,7,7]
    输出:1

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/longest-increasing-subsequence
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    class Solution {
        public int lengthOfLIS(int[] nums) {
            //动态规划
            if (nums == null || nums.length == 0){
                return 0;
            }
            //求最优解,利用dp,输出为长度,故new一维dp数组,dp[i]初始为1,状态为nums[i]和nums[j]大小比较,更新状态值
            //dp[i]表示的是长度为i + 1的数组最大递增子序列
            int len = nums.length;
            int result = 1;
            int []dp = new int[len + 1];
            Arrays.fill(dp, 1);	//填充dp数组,全部为1
            for (int i = 1; i < len; i++){
                for (int j = 0; j < i; j++){
                    if (nums[j] < nums[i]){	//判断条件
                        dp[i] = Math.max(dp[i], dp[j] + 1);	//状态转移
                    }
                    result = Math.max(dp[i], result);	//由于有子问题最大值,故设立变量result更新最大值
                }
            }
            return result; //输出dp[len - 1]为长度为len - 1的数组最大递增子序长度
        }
    }
    
  4. 爬楼梯
    1. 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

      每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

      示例 1:

      输入:n = 2
      输出:2
      解释:有两种方法可以爬到楼顶。

      1. 1 阶 + 1 阶
      2. 2 阶
        示例 2:

      输入:n = 3
      输出:3
      解释:有三种方法可以爬到楼顶。

      1. 1 阶 + 1 阶 + 1 阶
      2. 1 阶 + 2 阶
      3. 2 阶 + 1 阶

      来源:力扣(LeetCode)
      链接:https://leetcode.cn/problems/climbing-stairs
      著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    class Solution {
        public int climbStairs(int n) {
            //dp[i] 表示的是爬到第i阶方法数
            int []dp = new int[n + 1];
            dp[0] = 1;  //初始化dp[0]为1——>以至于后面for循环dp[2]能够得出正确答案,只要1和2是正确答案,后面可得正确取值
            dp[1] = 1; 
            for (int i = 2; i <= n; i++){
                dp[i] = dp[i - 1] + dp[i - 2]; //分为1步和2步两种情况,并且求种数,为两者之和
            }
            return dp[n];
        }
    }
    

总结:看完这几道例题,大家也许会发现主要还是dp一维数组,并且算是比较简单的,但是对于刚开始入门动规的话,应该能够起到一定的作用,力扣上面也有一个动态规划计划集,我暂时也是根据上面做的。大家有兴趣可以去跟着做一做。如有不足,请与我联系更改。

我把链接放在这里,大家有兴趣可以试试:力扣-动态规划入门

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值