常见动态规划题目

动态规划

适用范围:

  • 动态规划DP,在查找有多个多重叠子问题情况的最优解时有效。
  • DP只用于有最优子结构的问题(最优子结构:局部最优解完全能决定全局最优解),问题能够分解成子问题。

动态规划的特点:

  • 它将问题重新组合成子问题,为避免多次计算,保存每一次的结果。
  • 重点是找到状态转移方程

动态规划 vs 其他遍历算法
同:都是将问题拆解成子问题,然后求解。
异:
1)动态规划保留了子问题的解,避免重复计算。可以看作是带有记录状态的优先搜索。
2)动态规划是自下而上的。
3)带有记录的优先搜索是自上而下的,先解决从父问题都锁子问题。
4)如果题目需只需要最终状态,使用动态搜索方便;如果题目需要所有的路径,使用带有状态记录的优先搜索方便。

基本动态规划:一维

70、爬楼梯
给了一个楼梯总数n,每次只能爬一步或者两步,求爬这个n阶的楼梯一共有多少种可能性。
解答:
这个问题可以拆解成子问题,如果说已经知道了爬0-n-1中每一个高度阶梯有多少种方法,再求n阶楼梯有多少种可能方法。此时可以知道到第n阶阶梯的最后一步只能爬1/2步骤,因此dp[n] = dp[n-1]+dp[n-2]

class Solution {
public:
    int climbStairs(int n) {
        //使用动态规划解决 dp数组表示楼梯一共n阶时,有多少种方法可以爬到楼顶
        int size = max(n,2);
        int* dp = new int[size+1];
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i = 3;i<=n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

需要注意边界条件
优化:注意到其实dp[i]只与前两个元素有关,因此可以只用两个变量来存储。

class Solution {
public:
    int climbStairs(int n) {
        //使用动态规划解决 dp数组表示楼梯一共n阶时,有多少种方法可以爬到楼顶
        if(n<=2)return n;
        int pre1 = 1,pre2 = 2,cur;
        for(int i =3;i<= n;i++){
            cur = pre1+pre2;
            pre1 = pre2;
            pre2 = cur;
        }
        return cur;
    }
};

198、打家劫舍
给了一个nums数组,表示一条街上的每个房子的钱财数量,求一个劫匪在不同时打劫相邻两家的情况下,共能收获多少钱。
解答:使用dp数组,dp[i]表示当一共有i家店铺时,劫匪能打劫的最多钱财的数量。
那么dp[i] = max(dp[i-1],dp[i-2]+nums[i])//分为当前这房子打劫和不打劫两种情况,取其中的最大值。

class Solution {
public:
    int rob(vector<int>& nums) {
        //dp[i]表示在只有0~i的位置的房子时,一共能偷到的最大值
        //特判
        if(nums.size()==0)return 0;
        if(nums.size()==1)return nums[0];
        if(nums.size()==2)return max(nums[0],nums[1]);
        vector<int> dp(nums.size(),0);
        dp[0] = nums[0];
        dp[1] = max(nums[0],nums[1]);
        for(int i = 2;i<nums.size();i++){
            dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
        }
        return dp[nums.size()-1];
        

    }
};

同样可以使用空间压缩

413、等差数列拆分
给了一个数组,从它中拆出一组连续的值,是它的连续子数组,求共有多少个连续等差子数组。

解法:动态规划,dp表示以i为结尾的子数组中,连续等差子数组的个数。
注意到如果在dp【i】中,则num【i】与其前面连续的三个数能构成一个等差数组,这个数组的差已经是固定的了,如果num【i+1】与num【i】、num【i-1】能够成等差数组,则dp【i】中的所有子串+num【i+1】都可以构成等差数组,并且num【i+1】、num【i】、num【i-1】三个构成了一个新的等差数组。
因此dp【i】 = dp【i-1】+1;

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        //注意:子数组是数组中的一个连续序列 
        //dp[i]表示当只有元素0-n-1时,以i为结尾的等差数组个数。
        if(nums.size()<2)return 0;
        vector<int> dp(nums.size(),0);
        getans(dp,nums);
        return sum_dp(dp);

    }
    void getans(vector<int>& dp,vector<int>& nums){
        for(int i =2;i<nums.size();i++){
            if(nums[i]-nums[i-1]==nums[i-1]-nums[i-2]){
                dp[i] = dp[i-1]+1;
            }
        }
    }

    int sum_dp(vector<int>& dp){
        int sum = 0;
        for(int i = 0;i<dp.size();i++){
            sum = sum+dp[i];
        }
        return sum;
    }
};

基本动态规划:二维

64、最小路径和
给了一个二维数组,求从左上角到右下角的一条路径上的数字的总和的最小值。

解法:使用二维dp数组,其中dp[i][j]表示从(0,0)到(i,j)的路径上值之和的最小值。

注意:没有要求具体的路径,因此可以考虑dp,考虑到当前面的点的dp值都知道时,求dp[i][j]只有可能从(i-1,j)或者(i,j-1)位置来。(因为每次只能向右或者向下走)

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        if(m==0||n==0)return 0;
        vector<vector<int>> dp(m,vector<int>(n,0));
        for(int i = 0;i<m;i++){
            for(int j = 0;j<n;j++){
                if(i==0&&j==0)dp[i][j] = grid[i][j];
                else if(i==0)dp[i][j] = dp[i][j-1]+grid[i][j];
                else if(j==0)dp[i][j] = dp[i-1][j]+grid[i][j];
                else{
                    dp[i][j] = min(dp[i-1][j],dp[i][j-1])+grid[i][j];
                }
            }
        }
        return dp[m-1][n-1];

    }
};

优化:可以对空间优化,成为一维的

221、最大的正方形
给了一个由字符0、1组成的二维矩阵,求其中最大的全由1组成的正方形的面积大大小。

解法:注意到其中没有要求返回具体的东西,只是要返回一个结果,因此考虑使用动态规划实现。
设dp数组表示以ij为右下角的正方形的最大大小。去除了边界条件后,注意到某个位置的dp不仅和当前位置的0/1值有关,如果为0,则当前位置的dp为0,如果为1,则dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1。

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        //使用动态规划,dp[i][j]表示以ij为右下角的长方形的最大值边长
        int m = matrix.size();
        int n = matrix[0].size();
        int ret=0;
        vector<vector<int>> dp(m,vector<int>(n,0));
        for(int i = 0;i<m;i++){
            for(int j =0;j<n;j++){
                // cout<<"i="<<i<<",j="<<j<<endl;
                if(i==0||j==0){
                    // cout<<"i==0||j==0"<<endl;
                    dp[i][j] = matrix[i][j]-'0';
                    // cout<<"dp["<<i<<"]["<<j<<"]= "<<dp[i][j]<<endl;
                    ret = max(ret,dp[i][j]);
                    // cout<<"ret = "<<ret<<endl;
                }
                else{
                    if(matrix[i][j]=='1'){
                        dp[i][j] = 1+ min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]));
                        // cout<<"dp["<<i<<"]["<<j<<"]= "<<dp[i][j]<<endl;    
                        ret = max(ret,dp[i][j]);
                        // cout<<"ret = "<<ret<<endl;     
                    else{
                        dp[i][j]=0;
                    }
                }
            }
        }
        return ret*ret;
    }
};

注意:踩坑1、输入为char类型,2、返回值为面积

分割类问题

279、最优平方数
给一个正整数n,求他最少能用几个完全平方数的和组成。

解答:注意到没有要求求它的过程,并且发现当问题规模为0-n-1都知道时,dp[n] = min{dp[n-ii]+1}(ii<=n}。因此使用动态规划。



91、解码方式
给了以下字母和字符串的映射方式
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
给了一个数字字符串,求他能被用多少种字母串映射而成。
注意:07!=7,没有字母映射到的以0开头的。

解答:注意到只求个数,没有要求具体的解答。
当求dp[n]时,如果dp[0-n-1]都知道了,那么可以根据dp和s[n-1]和s[n]求出dp[n]
dp[n]表示在下标为0~n-1的字符串时,有多少种可能的方法。
要根据当前的字符和前一个字符的值来判断dp[n]=?
1、如果前一个值为0||>=3则后一个一定不能和前一个的值拼接。
1)如果此时值为0,则错误。
2、如果前一个值为1||2则要根据后一个点值判断
1)如果后一个值为0,那么必须和前一个拼接,dp[n]=dp[n-2]
2)如果pre==2&&后一个大于7,则一定不能和前一个拼接dp[n] = dp[n-1]
3)其他,i可以和前一个拼接,也可以不拼接dp[n]=dp[n-1]+dp[n-2]。

class Solution {
public:
    int numDecodings(string s) {
        //dp[i]表示在只有1-i位置的字符时,有多少种编码的可能
        int n =s.size();
        vector<int> dp(n+1,0);
        //特判
        //
        int pre = s[0]-'0';
        int cur;
        if(pre==0)return 0;
        if(n==0)return 0;
        if(n==1)return 1;
        dp[0] =1;
        dp[1] =1;
        for(int i =2;i<=n;i++){
            cur = s[i-1]-'0';
            //按照情况判断
            if(pre==0||pre>=3){
                if(cur==0)return 0;
                dp[i] = dp[i-1];
            }else{
                //pre == 1||2
                if(cur == 0)dp[i]=dp[i-2];
                else if(pre==2&&cur>=7)dp[i]=dp[i-1];
                else{
                    dp[i] = dp[i-1]+dp[i-2];
                } 
            }
            pre = cur;
        }
        return dp[n];
    }
};

注意:dp[0]应该初始化为1,表示前面的分割是唯一的。

子序列问题

300、最长递增子序列
给了一个数组,求这个数组中最长的递增子序列的长度。
子序列指的是从数组中按从前往后的顺序抽出的一组数,他们可以不相连。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n,1);
        int max_int = 0;
        //特判
        if(n==1)return 1;
        for(int i = 1;i<n;i++){
            int tmp = 1;
            for(int j = i-1;j>=0;j--){
                if(nums[i]>nums[j]){
                    tmp = max(dp[j]+1,tmp);
                }
            }
            dp[i] = tmp;
            max_int = max(dp[i],max_int);
        }
        return max_int;

    }
};

踩坑:注意每一个dp要找到前面一个数字比它小的最大的dp,而不是找到一个就停止了。

1143、最长公共子序列
给了两个字符串,求这两个字符串中最长的公共子序列的长度为多少。

解答:可以使用动态规划,二维动态规划,dp[I][J]表示在S1的到i位置,和S2到j位置两个串的最大公共子序列长度。
如果说S1【i-1】==S2【j-1】则这两个位置的值相等,要比两个串都少这个位置的值+1 dp[i][j]=dp[i-1][j-1]+1。
如果说两个位置的值不相等,则要选取dp[i-1][j]和dp[i][j-1]中更大的那个。因为dp表示的是两个串到当前位置的子串的最大公共子序列的长度。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
       int m = text1.length();
        int n = text2.length();
        vector<vector<int>> dp((m+1),vector<int>(n+1,0));
        // vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for(int i = 1; i<=m ;i++){
            for(int j = 1;j<=n;j++){
                if(text1[i-1]==text2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];

    }
};

背包问题

是一种组合优化的NP问题:有N个物品和容量为W的背包,每个物品有自己的体积w价值v,求拿哪些东西可以使得最大化所拿东西的价值。

0-1背包问题:每种东西只能拿0/1个。
完全背包问题/无界背包问题:每种东西能拿无限次数个。

注意到:可以使用二维动态规划来解决这个问题,其中dp[i][j]表示在前i个物品,重量不超过j情况下所获得的最大的价值。那么对于dp[i][j]如果不拿第i个物品,则dp[i][j] = dp[i-1][j];如果拿了第i个物品,则说明会占用一定的空间,dp[i][j] = dp[i][j-v[i-1];

0-1背包问题

int knapsack(vector<int> weights, vector<int> values, int N, int W){
	vector<vector<int>> dp(N+1,vector<int>(W+1,0));
	for(int  i =1; i<=N; i++)
	{
		int value = values[i-1];
		int weight = weights[i-1];
		for(int j = 1; j<=M; j++){
			//判断当前的东西能否被放进去
			if(weight <= j)
			{
				dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight]+value);
			}else{
				dp[i][j] = dp[i-1][j];
			}
		}
		return dp[N][W];
}

完全背包问题:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v,dp[i-1][j-2w]+2v,…);
但是考虑到dp[I][J-W]已经考虑过了dp[i][j-w] = max(dp[i-1][j-w]+v,dp[i-1][j-2w]+2v,…)
因此dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v);

int knapsack(vector<int> weights, vector<int> values, int N, int W){
	vector<vector<int>> dp(N+1,vector<int>(W+1,0));
	for(int  i =1; i<=N; i++)
	{
		int value = values[i-1];
		int weight = weights[i-1];
		for(int j = 1; j<=M; j++){
			//判断当前的东西能否被放进去
			if(weight <= j)
			{
				dp[i][j] = max(dp[i-1][j],dp[i][j-weight]+value);
			}else{
				dp[i][j] = dp[i-1][j];
			}
		}
		return dp[N][W];
}

空间压缩:
如果您实在不想仔细思考,这里有个简单的口诀:0-1 背包对物品的迭代放在外层,里层的
体积或价值逆向遍历;完全背包对物品的迭代放在里层,外层的体积或价值正向遍历。

416、分割等和子集
给了一个数组,求是否能等分为两个和一样的子数组。

先求和,然后使用dp找能否和为其一半的值。
使用二维dp,因为考虑有两个可以退的变量,一个是nums数组的个数,另一个是和。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        //先求和,然后使用dp找能否和为其一半的值。
        //使用二维dp,因为考虑有两个可以退的变量,一个是nums数组的个数,另一个是和。
        int m = nums.size();
        int sum = 0;
        for(int num: nums){
            sum += num;
        }
        if(sum%2==1)return false;
        sum = sum/2;
        //dp表示在有到数到第i个数时,和为j时,数组中是否有数的和为1/2sum
        vector<vector<bool>> dp(m+1,vector<bool>(sum+1,false));
        for(int i = 0;i<=m;i++){
            dp[i][0] = true;
        }
        for(int i =1;i<=m;i++){
            int num = nums[i-1];
            for(int j = 1;j<=sum;j++){
                if(num>sum)return false;
                else{
                    if(j-num>=0)
                        dp[i][j] = dp[i-1][j-num] || dp[i-1][j];
                    else{
                        dp[i][j] = dp[i-1][j];
                    }
                }
            }
        }
        return dp[m][sum];
    }
};

踩坑,首先初始化的时候要注意,dp[0][j]和dp[i][0]的值,以及其他所有的初始化,一般设置为false。
注意到j=0时,表示和为0,则一定是true。
注意到i=0&& j!=0时,表示的是 没有任何一个数,但和不为0,一定为false。

注意到当i!=0时,如果说num<j,此时dp不一定是false,可能由前面的几个拥有的值支撑起来。直接dp[i-1][j]即可。

474、一和零
给了一个01字符串的数组,给出两个整数m和n。
求这个数组的一个子数组,其中的所有0的个数为m,1的个数为n,求这个字数组的最大个数。

把0和1看作两个背包。把每个字符串看作一个物品。因此可以看成是背包问题。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int len = strs.size();
        vector<vector<vector<int>>> dp(len+1,vector(m+1,vector(n+1,0)));
        for(int i = 1;i<=len;i++){
            // cout<<"i="<<i<<endl;
            auto [count_0,count_1] = count(strs[i-1]);
            for(int j =0;j<=m;j++){
                // cout<<"j="<<j<<endl;
                for(int k = 0;k<=n;k++){
                    // cout<<"k="<<k<<endl;
                    dp[i][j][k] = dp[i-1][j][k];
                    // cout<<"dp = "<<dp[i][j][k]<<endl;
                    if((j-count_0)>=0&&(k-count_1)>=0){
                        // cout<<"if满足"<<endl;
                        dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-count_0][k-count_1]+1); 
                        // cout<<"dp = "<<dp[i][j][k]<<endl;
                    }
                }
            }
        }
        return dp[len][m][n];

    }
    pair<int,int> count(string str){
        int count_0 = 0;
        int count_1 = 0;
        for(char s:str){
            if(s=='0')count_0++;
            if(s=='1')count_1++;
        }
        return make_pair(count_0,count_1);
    }
};

注意:问题中出现了三个维度,因此使用了三维度数组,并且使用了pair和auto的组合。

322、零钱兑换
给了一个零钱数组,里面显示了各个硬币的价格,然后再给了一个amount。求最少能用多少个硬币来合成这个amount。

解题:可以使用dp,其中amount是背包,各个硬币为物品,每个硬币的value均为1,因此可以看作是完全背包问题。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //把amount看作背包,把每个价格的硬币看作货物,每个硬币的value均为1,因此可以看作是完全背包问题。
        int n = coins.size();
        vector<vector<int>> dp(n+1,vector<int>(amount+1,0));
        for(int j = 1;j<=amount;j++)dp[0][j] = -1;
        for(int i = 1;i<=n;i++){
            int coin_number = coins[i-1];
            for(int j =1;j<=amount;j++){
                if(dp[i-1][j]==-1){
                    if(j-coin_number>=0&&dp[i][j-coin_number]!=-1){
                        dp[i][j] = dp[i][j-coin_number]+1;
                    }else{
                        dp[i][j] = -1;
                    }
                }else{
                    dp[i][j] = dp[i-1][j];
                    if(j-coin_number>=0&&dp[i][j-coin_number]!=-1){
                        dp[i][j] = min(dp[i-1][j],dp[i][j-coin_number]+1);
                    }
                }
            }
        }
        return dp[n][amount];

    }
};

字符串编辑

650、只有两个按键的键盘
给了一个字符,有一个键盘每次只能做两件事情,复制全部/粘贴,求给个n,想要获得长度为n的这个字母组成的串最少需要多少次。

class Solution {
public:
    int minSteps(int n) {
        //使用动态规划
        vector<int> dp(n+1,0);
        //dp表示扩张到i需要的次数
        dp[1] = 1;
        if(n<=1)return 0;
        for(int i = 2;i<=n;i++){
            // cout<<"i = "<<i<<endl;
            dp[i]=i;
            for(int j = 2;j<=sqrt(i);j++){
                // cout<<"j = "<<j<<endl;
                if(i%j==0){
                    // cout<<"i%j==0"<<endl;
                    dp[i] = dp[j]+dp[i/j];
                    // cout<<"dp = "<<dp[i]<<endl;
                    break;
                }
            }
        }
        return dp[n];

    }
};

踩坑:注意到如果说不能被整除的数,只能从一开始翻倍,因此为他们赋予初值i。

10、

股票交易

股票交易类问题通常可以用动态规划来解决。对于稍微复杂一些的股票交易类问题,比如需
要冷却时间或者交易费用,则可以用通过动态规划实现的状态机来解决。

股票类问题:股票问题一共有六道:买卖股票的最佳时机(1(121),2(122),3(123),4(188))、含冷冻期、含手续费。本题是第一道,属于入门题目。

121、买卖股票最佳时机
给了一个股票每天价格的数组,求买入并在后面某天卖出的最大收益。

可以使用dp的方法,dp表示的是只有前i天时的最大收益
那么dp[i] = max(dp[i-1],price[i]-min_price)
并且需要一个参数一直记录最低的价格。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n<=1)return 0;
        int cur_min_buy = prices[0];
        vector<int> dp(n,0);
        for(int i = 1;i<n;i++){
            int price = prices[i];
            int earn = price - cur_min_buy;
            dp[i] = max(earn,dp[i-1]);
            cur_min_buy = min(price,cur_min_buy);
        }
        return dp[n-1];
    }
};

可以进行空间的压缩,判断本天卖出是不是比之前的最大大。

122、买卖股票的最佳时机II
给了一个股票每日的价格,求多次买入卖出后的最大收益。

可以使用dp,注意股票可以多次买卖,因此可能在每一天都可能持有股票或者现金,并且如果某天想卖出,在他的前一天必须处于已经持有股票的状态,如果想要买入,前一天必须没有股票。
因此使用二维的dp表示当前持有的是股票还是现金

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //如果要在第i天买入,需要知道只有i-1天时的最大的卖出的收益。
        //如果要在第i天卖出,需要知道只有i-1天时买入的最大的收益。
        //因此需要二维数组
        //dp[0]表示持有现金,dp[1]表示持有股票
        int n = prices.size();
        vector<vector<int>> dp(n,vector<int>(2,0));
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i = 1;i<n;i++){
            //想在今天持有现金的最大收益
            // cout<<"i = "<<i<<endl;
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
            // cout<<"dp[0]"<<dp[i][0]<<endl;
            //想在今天买入的最大收益
            dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
            // cout<<"dp[1]"<<dp[i][1]<<endl;
        }
        return dp[n-1][0];

    }
};

踩坑:注意不要使用dp表示今天是买入还是卖出,使用其表示今天持有股票还是现金能体现的更清楚。

123、买卖股票的最佳时机III
给了一个数组,表示股票每日的价格,求最大的股票收益是多少,最多只能买卖两次。

使用三维的dp。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        //使用三维dp,由于每天结束时有三个状态:1、日期,2、持有现金/股票,3、股票的卖出次数
        int n = prices.size();
        //特判
        if(n<=1)return 0;
        //初始化
        vector<vector<vector<int>>> dp(n,vector<vector<int>>(2,vector<int>(3,0)));
        dp[0][0][0] = 0;
        dp[0][1][0] = -prices[0];
        dp[0][1][1] = -prices[0];
        dp[0][1][2] = INT_MIN;
        for(int i =1;i<n;i++){
            //1、如果今日卖出过0次股票
            //当前持有现金,说明从未进行股票交易
            dp[i][0][0] = 0;
            //如果当前持有股票,说明买入过一次
            dp[i][1][0] = max(dp[i-1][1][0],dp[i-1][0][0]-prices[i]);
            //2、如果进入卖出过一次股票
            //如果当前持有现金
            dp[i][0][1] = max(dp[i-1][0][1],dp[i-1][1][0]+prices[i]);
            //如果持有股票
            dp[i][1][1] = max(dp[i-1][1][1],dp[i-1][0][1]-prices[i]);
            //3、如果买入卖出过两次股票
            //如果当前持有现金
            dp[i][0][2] = max(dp[i-1][0][2],dp[i-1][1][1]+prices[i]);
            //如果持有股票
            dp[i][1][2] = INT_MIN;
            
        }
        return max(dp[n-1][0][2],max(dp[n-1][0][1],dp[n-1][0][0]));

    }
};

注意dp[0][][1/2];

188、邮票买卖IV
给了一个邮票价格数组和k,求在最多买卖k次的情况下能最大获益多少。
可以使用三维dp来实现。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        //需要使用三维数组
        int n = prices.size();
        //特判
        if(n<=1)return 0;
        // k =min(k,n/2);
        vector<vector<vector<int>>> dp(n,vector<vector<int>>(2,vector<int>(k+1,0)));
        //初始化
        for(int i = 0;i<k;i++){
            dp[0][1][i] = -prices[0];
        }
        //dp[0][0][k] = 0;
        dp[0][1][k]=INT_MIN;
        //所有的dp[i][0][0]=0;
        //所有的dp[i][1][0]=-price[i];
        dp[0][1][0] = -prices[0];
        for(int i =1;i<n;i++){
            dp[i][1][0] = max(-prices[i],dp[i-1][1][0]);
            // cout<<"dp["<<i<<"][1][0] = "<<dp[i][1][0]<<endl;
        }
        //开始dp
        for(int i =1;i<n;i++){
            cout<<"i = "<<i<<endl;
            for(int t = 1;t<=k;t++){
                // cout<<"t = "<<t<<endl;
                // cout<<"dp[i-1][0][t] = "<<dp[i-1][0][t]<<endl;
                // cout<<"dp[i-1][1][t-1]+prices[i] = "<<dp[i-1][1][t-1]+prices[i]<<endl;
                dp[i][0][t] = max(dp[i-1][0][t],dp[i-1][1][t-1]+prices[i]);
                // cout<<"do[0] = "<<dp[i][0][t]<<endl;
                
                if(t==k){
                    dp[i][1][t] = INT_MIN;
                }else{
                    // cout<<"dp[i-1][1][t] = "<<dp[i-1][1][t]<<endl;
                    // cout<<"dp[i-1][0][t]-prices[i] = "<<dp[i-1][0][t]-prices[i]<<endl;
                    dp[i][1][t] = max(dp[i-1][1][t],dp[i-1][0][t]-prices[i]);
                    // cout<<"dp[1] = "<<dp[i][1][t]<<endl;
                }
            }
        }
        // int ret = 0;
        // for(int i = 0;i<=k;i++){
        //     ret = max(ret,dp[n-1][0][i]);
        // }
        // return ret;
        return dp[n-1][0][k];

    }
};
// 2
// [2,1,4,5,2,9,7]

踩坑:注意dp[I][1][k]不一定就是当天购买的,可能是前面任意一天购买的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值