精华内容
下载资源
问答
  • 动态规划经典例题

    2021-06-14 14:46:00
    恰好 和 不超过 切割次数不超过k次和恰好切割k次的区别仅仅在于dp[i][j],j>=i时的定义不同,一个是无效值,一个是有效值(有点像背包要求装满或不要求转满时的最优解) 所以装满和不要求装满,恰好k次和不超过k...

    恰好 和 不超过

    在这里插入图片描述
    切割次数不超过k次和恰好切割k次的区别仅仅在于dp[i][j],j>=i时的定义不同,一个是无效值,一个是有效值(有点像背包要求装满或不要求转满时的最优解)
    所以装满和不要求装满,恰好k次和不超过k次的 主要递推式其实是一样的,区别只在于初始化时,对相关变量的赋值不同

    如果是恰好切割k次,则对于j>=i,即长度为i的钢管一定切不了大于长度i的j次,所以它是一个无效解,且因为要求价值最大,所以赋值无效值为-inf
    如果是切割次数不超过k次,则对于j>=i,即长度为i的钢管切割不超过大于长度i的j次是有意义的,因为最多只能切割i次,所以此时的dp[i][j]不是无效值而是r(n,n)

    但有趣的是他们主要的递推方程都是一样的,
    r(n,k)=max(p[i]+r(n-i,k-1) ,1=<i<=n,因为这里的i是可以等于n的所以第一次切n就相当于没有切

    在这里插入图片描述

    DP典例

    • 最长递增子序列LIS->Asc[i](设为以a[i]结尾的最长子序列,因为添加下一个元素时必须看前一个末尾元素是否必现在的大)
      -Asc[i]=max(Asc[j])+1(j<i&&a[j]<=a[i])
    • 最长公共子序列LCS->dp[i][j](比较两个字符串末尾的两个字符)
      i=0||j=0 dp[i][j]=0
      s[i]==s[j] dp[i][j]=dp[i-1][j-1]+1
      s[i]!=s[j] dp[i][j]=max(dp[i][j-1],dp[i-1][j])

    最长公共子序列变题
    考虑最后一个字符的同时还要考虑是特殊字符的前面一个字符的相等情况
    在这里插入图片描述

    • 正则表达式
      在这里插入图片描述

    • 最长回文子序列->dp[i][j](比较一个字符串的两个端点字符是否相同)
      i==j dp[i][j]=1
      //s[i]==s[j] dp[i][j]=dp[i-1][j-1]+2
      s[i]==s[j] dp[i][j]=dp[i+1][j-1]+2
      //s[i]!=s[j] dp[i][j]=max(dp[i][j-1],dp[i-1][j-1])
      s[i]!=s[j] dp[i][j]=max(dp[i][j-1],dp[i+1][j])

    • 矩阵链->dp[i][j]

    • 最优二叉搜索树->dp[i][j]

    • 石子合并
      dp[i][j]=max(dp[i][k]+dp[k+1][j]+sum(i,j))
      考虑最后两个数而不是考虑第一步是怎么选,因为只考虑第一步无法将整个集合分为左右两个独立的子问题,因为他们可能还可以和第一步合成的新数进行下一步合并
      所以考虑最后一步的两个数,最后一步的两个书一定是由两个独立的子块各自合并得来的
      对于相邻元素合并等问题都可以考虑成最后一步两个独立元素的的合并!!!

    所以对于区间dp除了考虑第一步外还可以考虑最后一步

    • 编辑距离
      在这里插入图片描述
      dp[i][j]指把**word1[0…i - 1]转换为word2[0…j - 1] **的最小操作数(word1->word2)。
      边界条件:
      dp[i][0] = i; 从长度为 i 的字符串转为空串 要删除 i 次
      dp[0][j] = j. 从空串转为长度为 j 的字符串 要添加 j 次
      一般情况:
      如果word[i - 1] == word2[j - 1],则dp[i][j] = dp[i - 1][j - 1],因为不需要进行操作,即操作数为0.
      如果word[i - 1] != word2[j - 1],则需考虑三种情况,取最小值:

    Replace word1[i - 1] by word2[j - 1]: (dp[i][j] = dp[i - 1][j - 1] + 1 (for replacement));
    Delete word1[i - 1]: (dp[i][j] = dp[i - 1][j] + 1 (for deletion));
    Insert word2[j - 1] to word1[0…i - 1]: (dp[i][j] = dp[i][j - 1] + 1 (for insertion)).

    就算末尾两个字符相等,也不是说就一定要匹配,也可以不匹配再添加一个相同的去匹配,让i去匹配j-1.如bcacc,bcac
    在这里插入图片描述

    在这里插入图片描述
    注意编辑距离初始化时不是把全部0行,0列都初始化为0的,而是初始化为0,1,2,…,n

    • 删除k个以内的字符的不同字符串个数->dp[n][k]
      dp[i][j]=dp[i-1][j](末尾为a[i]前面再删j个以内)+dp[i-1][j-1](末尾不为a[i]前面再删j-1个以内),和切钢管相同因为末尾位置不同,所以不会出现两个dp里面有重复的
      但如果末尾为a[i] 可能会有重复字符串,所以之后要减去重复的字符串,即找前面最近和a[i]相等的一个字符,如果两者之间的距离小于删除的字符数则-dp[i-d-1][j-d],即
      dp[k−1][j−(i−k)](a[k]==a[i])
    • n个任务分成k块,要使k块里面最大的数最小
      s[i,j]= s[i]+s[i+1]+…+s[j], (s[i,j]=0, if i>j)
      在这里插入图片描述

    ps:

    • 奇怪的打印机(直接比较两个端点)
      在这里插入图片描述
    • 最长有效括号
      在考虑最后一个字符的同时考虑倒数第二个字符
      在这里插入图片描述

    s[i]=’)’&&(s[i-1]‘)’) dp[i]= dpi-1]+dp[i - dp[i-1]-2]+2(和i-1前面已经匹配好的前一个’(‘进行匹配)
    s[i]=’)’&&(s[i-1]
    ’(’) dp[i]= dp[i-2]+2(直接匹配)

    • lc740. 删除并获得点数(打家劫舍变题)
      变删除相邻数值问题为不能抢劫相邻的房间价值最大问题
      首先需要变数值为下标及房间编号
    展开全文
  • 动态规划课件及动态规划经典例题讲解和分析。主要介绍动态规划算法的思想和例题的分析。
  • 股票买卖-动态规划经典例题 【题目描述】 最近越来越多的人都投身股市,阿福也有点心动了。谨记着“股市有风险,入市需谨慎”,阿福决定先来研究一下简化版的股票买卖问题。 假设阿福已经准确预测出了某只股票在未来...

    股票买卖

    【题目描述】

    最近越来越多的人都投身股市,阿福也有点心动了。谨记着“股市有风险,入市需谨慎”,阿福决定先来研究一下简化版的股票买卖问题。

    假设阿福已经准确预测出了某只股票在未来N天的价格,他希望买卖两次,使得获得的利润最高。为了计算简单起见,利润的计算方式为卖出的价格减去买入的价格。

    同一天可以进行多次买卖。但是在第一次买入之后,必须要先卖出,然后才可以第二次买入。

    现在,阿福想知道他最多可以获得多少利润。

    【输入】

    输入的第一行是一个整数T(T≤50),表示一共有T组数据。

    接下来的每组数据,第一行是一个整数N(1≤N≤100,000),表示一共有N天。第二行是 N 个被空格分开的整数,表示每天该股票的价格。该股票每天的价格的绝对值均不会超过1,000,000。

    【输出】

    对于每组数据,输出一行。该行包含一个整数,表示阿福能够获得的最大的利润。

    【输入样例】

    3
    7
    5 14 -2 4 9 3 17
    6
    6 8 7 4 1 -2
    4
    18 9 5 2

    【输出样例】

    28
    2
    0

    【提示】

    对于第一组样例,阿福可以第1次在第1天买入(价格为5),然后在第2天卖出(价格为14)。第2次在第3天买入(价格为-2),然后在第7天卖出(价格为17)。一共获得的利润是(14-5)+(17-(-2))=28。

    对于第二组样例,阿福可以第1次在第1天买入(价格为6),然后在第2天卖出(价格为8)。第2次仍然在第2天买入,然后在第2天卖出。一共获得的利润是8-6=2。

    对于第三组样例,由于价格一直在下跌,阿福可以随便选择一天买入之后迅速卖出。获得的最大利润为0。

    经典算法Baidu搜索,深刻体会。

    【算法分析】

    解法一

    使用两个数组
    p 表示 第 i 天卖出可获得最大利益
    当天的价格 - 前面天的最低价

    int min1=0x3f;
    		for(i=1;i<=n;i++)
    		{
    			min1=min(min1,a[i]);//前些天的最低价格
    			p[i]=a[i]-min1;
    		}
    

    q 表示 第 i 天买入可获得最大利益
    后面天的最高价格 - 当天的价格

    int max1=-0x3a; 
    	for(i=n;i>0;i--)
    	{
    		max1=max(max1,a[i]);//后面天的最高价格
    		q[i]=max1-a[i];
    	}
    

    两次买入卖出的最高价格

    前些天卖出的最高获利 + 这天买入的最高获利这天的买入卖出两次的最高价格

    这些答案的最大值 就是这些天买入卖出两次的最高获利

    int tmp=-0x3a,ans=-0x3a;
    	for(i=1;i<=n;i++)
    	{
    		tmp=max(tmp,p[i]);//表示前些天卖出的最大利益
    		//表示前些天卖出的最大利益+这天买入的最大利益
    		//则为这些天两次买入卖出的最大利益 
    		ans=max(ans,tmp+q[i]);	
    	} 
    

    【源代码】

    #include<bits/stdc++.h>
    using namespace std;
    int a[100005];
    int p[100005];//第i天卖出可获最大利益 
    int q[100005]; //第i天买入可获最大利益 
    int main()
    {
    	int n1,l;
    	int n,i,j,k;
    	cin >> n1;
    	for(l=0;l<n1;l++)
    	{
    		cin >> n;
    		for(i=1;i<=n;i++) cin >> a[i];
    		memset(p,0,sizeof(p));
    		memset(q,0,sizeof(q));
    		//第 i 天卖出可获最大利益 当天价格 - 前面几天的最低价 
    		int min1=0x3f;
    		for(i=1;i<=n;i++)
    		{
    			min1=min(min1,a[i]);
    			p[i]=a[i]-min1;
    		}
    		//第 i 天买入获得的最大利益 后面天的最大值 - 当天价格
    		int max1=-0x3a; 
    		for(i=n;i>0;i--)
    		{
    			max1=max(max1,a[i]);
    			q[i]=max1-a[i];
    		}
    		int tmp=-0x3a,ans=-0x3a;
    		for(i=1;i<=n;i++)
    		{
    			tmp=max(tmp,p[i]);//表示前些天卖出的最大利益
    			//表示前些天卖出的最大利益+这天买入的最大利益
    			//则为这些天两次买入卖出的最大利益 
    			ans=max(ans,tmp+q[i]);	
    		} 
    		cout << ans << endl;
    	}
    	return 0;
    }
    
    
    展开全文
  • 合并石子-动态规划经典例题【问题描述】【输入格式】【输入样例】【输出样例】【算法分析】【源代码】 【问题描述】 在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆...

    【问题描述】

    在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
    ##【编程任务】
    试设计一个程序,计算出将N堆石子合并成一堆的最小得分。

    【输入格式】

    第一行为一个正整数N (2≤N≤100);
    以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。

    【输出格式】

    为一个正整数,即最小得分。

    【输入样例】

    7
    13
    7
    8
    16
    21
    4
    18
    

    【输出样例】

    239
    

    【算法分析】

    首先,我们看题目的意思,就是将相邻的两堆合成一堆,我们创建一个数组 s 方便我们计算相邻堆之间的和

    下面看一下递推方程

    i->合并石子的开始堆 从后开始 n-1 开始 到 1

    j->合并石子的结束堆 从 i+1开始 到 末尾 n

    k->从每个 i~j 也就是将每种合并 以k的位置 分成两份石子合并,然后找出费用最少的一种分法 放到f[i][j] 也就是i~j 堆石子合并的最少费用

    用s[i]表示前i堆石头的价值总和,f[i][j]表示把第i堆到第j堆的石头合并成一堆的最优值

    for (i=n-1;i>=1;i–) // n-1~0
    for (j=i+1;j<=n;j++) // i+1~n
    for (k=i;k<= j-1;k++) // i~j
    f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);

    输出f[1][n]

    【源代码】

    #include<bits/stdc++.h>
    using namespace std;
    int n;
    int a[105];//每堆石头的数量
    int f[105][105];//第i~j堆石头的最优解
    int s[105];//前i堆石头的和
    int main()
    {
    	int i,j,k,sum=0;
    	cin >> n;
    	for(i=1;i<=n;i++)
    	{
    		cin >> a[i];
    		sum+=a[i];
    		s[i]=sum;
    	}
    	memset(f,127/3,sizeof(f)); 
    	for(int i=1;i<=n;i++)//堆到自己的合并最优是 0 
            f[i][i]=0;
    	//f[i][j] 把第i堆 到 第j堆 的石头合并成最优值 
    	for(i=n-1;i>0;i--)//n-1 ~ 1
    		for(j=i+1;j<=n;j++)//i+1 ~ n
    		for(k=i;k<j;k++)//i ~ j
    		{                  //i~k的最优解 + k+1~j的最优解 + i~j的总和  
    			f[i][j]=min(f[i][j],f[i][k] + f[k+1][j] + s[j]-s[i-1]); 
    		}
    		cout << f[1][n];
    	return 0;
    }
    
    
    展开全文
  • 动态规划是最重要、最经典的算法之一,学好动态规划对我们十分重要,掌握动态规划对解决某些问题会起到事半功倍的效果。 动态规划: 特点: ①可以把原始问题划分为一系列子问题 ②求解每个子问题仅一次,并将其结果...

    引言:

    动态规划是最重要、最经典的算法之一,学好动态规划对我们十分重要,掌握动态规划对解决某些问题会起到事半功倍的效果。

    动态规划:

    特点:
    ①可以把原始问题划分为一系列子问题
    ②求解每个子问题仅一次,并将其结果保存到一个表中,以后用到时直接存取,不重复计算,节省时间。
    ③自底向上地计算
    适用范围:
    原问题可以分为多个相关子问题,子问题的解会被重复使用。

    动态规划的关键点:

    动态规划和递归或者分治没有根本上的区别(关键看有无最优子结构)。
    **共性:**找到重复子问题
    **差异性:**最优子结构、中途可以淘汰次优解。

    动态规划题目的特点:

    1.计数
    -有多少种方式走到右下角
    -有多少种方法选出k个数的和为sum

    2.求最大最小值
    -从左上角到右下角路径的最大数字和
    -最长上升子序列长度
    3.求存在性
    -取石子游戏,先手是否必胜
    -能不能选出k个数使得和是sum

    下面我会用一些力扣上的动态规划经典例题来解释动态规划的用法。请大家将题目和上面的问题特点对号入座。

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

    在这里插入图片描述
    题解:
    这道题可以从前往后推,如果目标钱数是0,凑成总金额所需的最少的硬币个数是多少;
    目标是1,会怎样呢,…直到目标钱数为amount,需要的最少硬币又是多少呢?
    首先要看最后一步,最后一步肯定是dp[i]置为dp[i-coins[j]]+1中最小的那个。所以转换为正推,
    如果无法凑够则先把硬币数dp[i]置为最大INT_MAX;否则,则把dp[i]置为最小的dp[i-coins[j]]+1.
    然后最后判断一下,如果dp[amount]是否为INT_MAX,如果是,说明无法凑齐,返回-1,如果不是,说明dp[amount]就是最少硬币数。
    重要的就是转移方程:dp[i]=min(dp[i],dp[i-coins[j]]+1);

    代码:

    class Solution {
    public:
        int coinChange(vector<int>& coins, int amount) {
            int dp[amount+1];//因为下面dp[i]=INT_MAX,如果再加1,就会超过范围
            dp[0]=0;
            int i,j;
            for(i=1;i<=amount;i++)
            {
                dp[i]=INT_MAX-10000;
                for(j=0;j<coins.size();j++)
                {
                    if(i>=coins[j])
                    {
                        dp[i]=min(dp[i],dp[i-coins[j]]+1);
                    }
                }
            }
            return dp[amount]==INT_MAX?-1:dp[amount];
        }
    };
    

    题目2:不同路径
    题目链接
    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
    问总共有多少条不同的路径?
    在这里插入图片描述
    在这里插入图片描述
    题解:
    我们首先看最后一步,要想到达(m,n)首先要到达(m-1,n)或者(m,n-1)。设到达(m-1,n)路径有x种,到达(m,n-1)路径有y种,则到达(m,n)的路径有x+y种。
    然后再从头看起,到达(1,2)路径只有1种,到达(2,1)路径只有1种,到达(i,j)的路径数是到达(i-1,j)和(i,j-1)的路径和。
    重点:转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1];

    代码:

    class Solution {
    public:
        
        int uniquePaths(int m, int n) {
            int dp[m][n];
            int i,j;
            dp[0][0]=0;
            for(i=0;i<m;i++)
            {
                for(j=0;j<n;j++)
                {
                    if(i==0||j==0)
                    {
                        dp[i][j]=1;
                    }
                    else
                    {
                        dp[i][j]=dp[i-1][j]+dp[i][j-1];
                    }
                }
            }
            return dp[m-1][n-1];
        }
    };
    

    题目3:跳跃游戏
    题目链接
    给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
    在这里插入图片描述
    题解:
    我们首先从后面看,要想到达下标为n-1的地方,有两个条件:①能到达下标为 i 的地方;②从 i 能调到 j 。所以我们就得到了转移方程:if(dp[j]&&j+nums[j]>=i) dp[j]=true;
    然后我们再从头遍历,判断每个下标为 i 的地方算法能调到,最后返回dp[n-1]即可。

    代码:

    class Solution {
    public:
        bool canJump(vector<int>& nums) {
            int i,j;
            bool dp[nums.size()];
            dp[0]=true;
            for(i=1;i<nums.size();i++)
            {
                dp[i]=false;
                for(j=0;j<i;j++)
                {
                    if(dp[j]&&j+nums[j]>=i)
                    {
                        dp[i]=true;
                        break;
                    }
                }
            }
            return dp[nums.size()-1];
        }
    };
    

    题目4:背包问题

    因为背包问题比较重要,所以我单独把背包问题拿出来单独写一篇博客。
    传送门

    题目5:最大子列和
    题目链接
    给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

    在这里插入图片描述
    题解:
    如果数组中只有1个数,那么最大子列和就是该数值。如果大于1个,依次遍历数组,如果没考虑当前数的时候,最大子列和小于0,那么考虑到这个数时最大子列和就是该数值;如果没考虑当前数的时候,最大子列和小于0,那么考虑到这个数时最大子列和就是该数值加上之前的最大子列和。然后从这些子列和中选出最大的即可。(可以直接看代码,代码通俗易懂.)

    代码:

    class Solution {
    public:
        int maxSubArray(vector<int>& nums) {
        int dp[nums.size()];
        int i;
        dp[0]=nums[0];
        int maxSum=dp[0];
        for(i=1;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1],0)+nums[i];
            maxSum=maxSum>dp[i]?maxSum:dp[i];
        }
        return maxSum;
        }
    };
    

    动态规划总结:

    在这里插入图片描述

    展开全文
  • 动态规划经典例题

    2019-09-09 22:30:32
    文章目录动态规划(Dynamic Programming)概念DP定义:动态规划具备了以下三个特点动态规划的本质从四个角度考虑动态规划问题状态定义的要求第一题 Fibonacci动态规划方法第2题 变态青蛙跳台阶(Climbing Stairs) 动态...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,845
精华内容 1,138
关键字:

动态规划经典例题