动态规划-Dynamic programming

动态规划:将一个问题拆成几个子问题,分别求解这些子问题,即可推断出大问题的解。

判断一个问题能否使用DP解决:能将大问题拆成几个小问题,且满足无后效性、最优子结构性质

DP的核心思想:尽量缩小可能解空间。

暴力做法是枚举所有的可能解,这是最大的可能解空间。
DP是枚举有希望成为答案的解。这个空间比暴力的小得多。

参考阮行止的回答

例1、青蛙跳台阶问题

题目链接

在这里插入图片描述

思想

对该问题进行抽象,n个阶梯,每个阶梯都代表一个位置,然后将这个n个位置相连(只能用长度为1和2的线)
在这里插入图片描述
这个问题就转化成了从 位置0 到 位置10 有几种不同的路可以走?

递推关系dp[n] = dp[n-1] + dp[n-2];

比如710有几种走法=810的走法+910的走法

递推结果可以由下面这个数组说明

在这里插入图片描述
参考牛岱的回答

代码

class Solution {
public:
    int numWays(int n) {
        vector<int> dp(n+1,1);
        for(int i=2;i<=n;i++){
            dp[i]=(dp[i-1]+dp[i-2])%1000000007;
        }
        return dp[n];
    }
};

例2、最长上升子序列

题目链接
在这里插入图片描述

思想

方法1O(n2)做法

在这里插入图片描述
参考

代码

对应方法1

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size());
        for(int i=0;i<nums.size();i++){
            dp[i]=1;
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]&&dp[j]+1>dp[i])
                    dp[i]=dp[j]+1;
            }
        }
        //这里我调用逆序函数,返回首部不晓得为啥不行
        // sort(dp.rbegin(),dp.rend());
        // return dp[0];
        int res=0;
        for(int i=0;i<dp.size();i++){
            if(res<dp[i])
                res=dp[i];
        }
        return res;
    }
};
二分查找解法

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //二分查找解法
        vector<int> top=nums;
        // 牌堆数初始化为 0
        int piles = 0;
        for (int i = 0; i < nums.size(); i++) {
            // 要处理的扑克牌
            int poker = nums[i];
            /***** 搜索左侧边界的⼆分查找 *****/
            int left = 0, right = piles;
            while (left < right) {
                int mid = (left + right) / 2;
                if (top[mid] > poker) {
                    right = mid;
                } 
                else if (top[mid] < poker) {
                    left = mid + 1;
                } 
                else {
                    right = mid;
                }
            }  
            // 没找到合适的牌堆, 新建⼀堆
            if (left == piles) piles++;
            // 把这张牌放到牌堆顶
            top[left] = poker;
        }
        // 牌堆数就是 LIS ⻓度
        return piles;
    }
};
对上诉二分查找代码进行分析
序列为2 5 3 4 1 7 6

下图为i=0时刚进入循环

在这里插入图片描述

while循环前

在这里插入图片描述

i=1

在这里插入图片描述
在这里插入图片描述

例3、剪绳子

题目链接
在这里插入图片描述

思想

参考出处

动态规划5步走

1. 判题题意是否为找出一个问题的最优解
2. 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题
3. 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程)
4. 讨论底层的边界问题
5. 解决问题(通常使用数组进行迭代求出最优解)

5步走可知
  1. 判题题意是否为找出一个问题的最优解 看到字眼是“可能的最大乘积是多少”,由此判断是求最优解问题,可以用动态规划解决;

  2. 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题。 题目中举了个例子:当绳子的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最大乘积是18;我们可以从这里开始突破,把长度为8绳子的最大乘积分解为数个子问题,长度为8我们可以把它看成长度为1和7的绳子的和,或者长度为2和6的绳子的和,或者长度为3和5的绳子的和and so on! 到这里,相信大家已经看到一丝真理了吧?

  3. 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程) 在第二点时,我们已经从上到下分析问题了,现在我们要从下往上分析问题了。分析可知, f(8)的值就是f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)它们之中的最大值,即f(8) =
    Max{f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)}
    只要知道f(1)到f(7)的值就能求出f(8);对于f(7),只要知道f(1)到f(6)的值就能求出f(7);对于f(6),只要知道f(1)到f(5)的值就能求出f(6);以些类推,我们只要知道前几个边界的值,就能一步步迭代出后续的结果!
    状态转移方程: f(n)=Max{f(n-i)*f(i)} i={1,2,3,…,n/2}

  4. 讨论底层的边界问题 底层的边界问题说的就是最小的前几个数值的f(n)的值,本题中就是f(0)、f(1)、f(2)、f(3)的值 。
    对于f(0),长度为0的绳子,没办法剪,没有意义
    对于f(1),长度为1的绳子,没办法剪,设为1
    对于f(2),长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,但剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大值的话就没必要剪。
    对于f(3),长度为3的绳子,只有一种剪法,剪成两段长度为1和2的绳子,但剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪。

代码

Java版本

public static int cutting(int n) {
        //长度小于等等于1没办法剪
        if(n <= 1)
            return 0;
        //对于f(2),长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,剪后的乘积为1
        if(n == 2)
            return 1;
        //对于f(3),长度为3的绳子,只有一种剪法,剪成两段长度为1和2的绳子,但剪后的乘积为2
        if(n == 3)
            return 2;
        int max = 0;
        //数组用于存储绳子乘积最大值
        int value[] = new int[n + 1];
        value[0] = 0;
        value[1] = 1;
        //剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大值的话就没必要剪
        value[2] = 2;
        //剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪
        value[3] = 3;
        //从f(4)开始迭代
        for(int i = 4;i <= n; i++) {
            max = 0;
            for(int j = 1;j <= i/2; j++) {
                int val = value[j] * value[i - j];
                max = val > max ? val : max;
            }
            value[i] = max;
        }
        max = value[n];
        return max;
}
C++版本

class Solution {
public:
    int cuttingRope(int n) {
        vector<int> dp(n+1,0);
        dp[0]=1;
        for(int i=1;i<=(n+1)/2;i++){
            for(int j=i;j<=n;j++){
                dp[j]=max(dp[j],dp[j-i]*i);
            }
        }
        return dp[n];
    }
};
下面是上诉的c++代码的执行过程

在这里插入图片描述

北大OJ
PAT
codeup
C++的常用函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值