zoukankan      html  css  js  c++  java
  • 算法学习——动态规划讲解

    一、概念

    通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

    二、题型特点

    • 计数
      • 有多少种方式走到最右下角
    • 求最大值最小值
      • 从左上角走到右下角的最大数字和
    • 求存在性
      • 能否选出k个数使得和为sum

    三、如何使用动态规划

    这里先看一道LeetCode题。从这道题来学习如何使用动态规划。

    题目链接LeetCode-322

    Coin Change

    给定不同面额的硬币 coins 和一个总金额 amount。
    编写一个函数来计算可以凑成总金额所需的最少的硬币个数。
    如果没有任何一种硬币组合能组成总金额,返回 -1。
    

    示例

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

    该题是一个求最大最小的动态规划算法题。

    与递归解法相比,没有重复计算。

    3.1 组成部分一:确定状态

    确定状态需要有两个注意的点:最后一步子问题

    1.最后一步

    肯定是(k)枚硬币加起来等于11。最后一枚硬币值假设是(a_k),则剩下的(k-1)枚硬币的值为(11-a_k)

    由于是最优解,则11-(a_k)的硬币数一定是最少。

    2.子问题

    将原问题转换为子问题,最少用多少枚硬币拼出(11-a_k)

    那么(a_k)到底是多少,因为有3枚硬币,所以只可能是1、2、5中的一个。

    子问题方程如下:

    (f(11) = min{f(11-1)+1,f(11-2)+1,f(11-5)+1})

    f(11)为拼出面值为11所需的最少硬币数。

    根据以上,使用递归的解法:

    
    public class Dp1 {
    
        public int getMinCoin(int X) {
    
            if (X == 0) return 0;
            int res = 10000;
            if (X >= 1) {
                res =  Math.min(getMinCoin(X - 1)+1, res);
            }
            if (X >= 2) {
                res =  Math.min(getMinCoin(X - 2)+1, res);
            }
            if (X >= 5) {
                res =  Math.min(getMinCoin(X - 5)+1, res);
            }
            return res;
        }
    
        public static void main(String[] args) {
            Dp1 dp1 = new Dp1();
            int result = dp1.getMinCoin(11);
            System.out.println(result);
        }
    
    }
    
    

    在这里插入图片描述
    使用递归来解决,有比较多的重复计算,效率比较低。

    动态规划会保存计算结果,来避免递归重复计算的问题。

    3.2 组成部分二:转移方程

    动态规划的解法

    状态f[X]表示,面值为X所需的最小硬币数。
    对于任意的X,满足
    (f[X] = min{f[X-1]+1,f[X-2]+1,f[X-5]+1})

    意思就是获取面值大小为X最少需要的硬币数 = 从(最后一个硬币选1时,剩下的要凑面值为X-1所需要的最少硬币数,因为最后一个硬币选了1,所以硬币数要+1,即f[x-1]+1)、(f[X-2]+1)、(f[X-5]+1)中选择一个最少的硬币数。

    3.3 组成部分三:初始条件和边界情况

    设置初始值,考虑边界情况。

    3.4 组成部分四:计算顺序**

    从上到下,从左到右。

    四、LeetCode题完整解法

    
    class Solution {
        public int coinChange(int[] coins, int amount) {
            
            int[] f = new int[amount+1];
            int coin_num = coins.length;
            //初始条件
            f[0] = 0;
            //f[x] = min{f[x-c1]+1,f[x-c2]+1,f[x-c3]+1}
            for(int x = 1;x<=amount;x++){
                f[x] = Integer.MAX_VALUE;
                for(int i = 0;i<coin_num;i++){
                    // 考虑输入[2],4,则需要保证f[x-coins[i]] != Integer.MAX_VALUE,即f[x-coins[i]]必须要是存在的状态
                    if(x >=coins[i] && f[x-coins[i]] != Integer.MAX_VALUE){
                        f[x] = Math.min(f[x-coins[i]]+1,f[x]);
                    }
                }
            }
            // 考虑输入[2],3,则amount = -1
            if(f[amount] == Integer.MAX_VALUE){
                return -1;
            }
            return f[amount];
        }
    }
    
    

    参考文档

    动态规划

    参考视频

    动态规划入门 Introduction to Dynamic Programming
    ACM专题讲解:DP动态规划
    算法数据结构面试通关(经验全集)

  • 相关阅读:
    一个校园互联网实验室的纳新试题(试题整理)
    用CI框架向数据库中实现简单的增删改查
    CodeIgniter框架学习要点
    一个理解PHP面向对象编程(OOP)的实例
    为什么在需要使用‘template as a disambiguator’
    被忽略的class“特殊成员”—转换函数
    C++风格cast的优先级
    printf当前正在处理的格式化符是什么
    C++中模板特殊化(speicialization)的偏序关系及make规则选择
    PhysX中raycast和sweep对block和touch的处理逻辑
  • 原文地址:https://www.cnblogs.com/fonxian/p/10854654.html
Copyright © 2011-2022 走看看