LeetCode算法练习——动态规划提高(五)

LeetCode309. 最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  •     你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  •     卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:

输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

定义状态:我们设置一个数组dp[len][2],第二个维度为1时表示此时持有股票,为0表示未持有股票

状态方程:

  • dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); // 第i天不持有股票,此时我们来记录先前抛售利润的最大值
    • case1:前一天买入股票,我们当天抛售;
    • case2:前一天未买入股票。
  • dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); // 第i天,持有股票,这时应让持有的股票值最小,这样相减时利润最大
    • case1:冷却期结束也未买入(即前两天为冷却期,前一天也未买入),我们当天买入;
    • case2:前一天买入股票。

边界情况:数组长度小于等于2的情况

初值定义:

  • dp[0][0] = 0;                                       // 第一天,不持有股票
  • dp[0][1] = prices[0];                               // 第一天,持有股票
  • dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]);     // 第二天,不持有股票
  • dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]);     // 第二天,持有股票
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0; // 特殊情况处理

        if(len == 2 && prices[0] >= prices[1]) return 0;    // 特殊情况处理
        else if(len == 2 && prices[0] < prices[1]) 
            return prices[1] - prices[0];                   // 特殊情况处理
        vector<vector<int>> dp(len, vector<int>(2, 0));
        dp[0][0] = 0;                                       // 第一天,不持有股票
        dp[0][1] = prices[0];                               // 第一天,持有股票
        dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]);     // 第二天,不持有股票
        dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]);     // 第二天,持有股票
        for(int i = 2; i < len; i++){
            dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); 
            // 第i天,不持有股票,可能此时已抛售这时应让利润最大
            dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); 
            // 第i天,持有股票,这时应让持有的股票值最小
        }
        return dp[len - 1][0];
    }
};

LeetCode123. 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

状态表示

与上一题类似用 dp(i, j) 表示第 i 天进行 j 次交易的最大利润。

状态属性

可以看到题目要求是最大利润,所以属性就是求一个最大值。

状态计算

这个是最难的一步,方法就是从 dp(i,j) 表示的 实际意义 出发,尽可能地划分出所有的条件。比如本题,我们从关键词第 i 天,第 j 次交易进行思考,不难发现第 i 天可以划分两种情况:

  •     不交易:第 i 天什么都没发生,交易次数 j 不变,利润为前一天的利润:dp(i, j)=dp(i − 1, j);
  •     交易:利润为前 j−1 次交易的利润与第 i 天交易的利润之和:dp(i, j)=dp(k − 1, j − 1) + prices(i) − prices(k);

由于买入卖出为一次交易,因此准确来讲如果在第 i 天进行交易就是指在第 i 天进行卖出操作,那么是何时买入的呢?如果买入为第 k 天,则 k 满足 k∈[1,...,i]。
算法

由于重复计算 k 会多嵌套一层循环导致代码超时,因此需要将第一笔交易获得的利润整合到第二笔交易的成本中。

dp(i, j)=prices(i) − (prices(k) − dp(k − 1, j − 1));前者表示第 i 天卖出价格,后者表示第 k 天买入价格减去第1次交易利润

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;                              // 特殊情况处理
        if(len == 2 && prices[0] >= prices[1]) return 0;    // 特殊情况处理
        else if(len == 2 && prices[0] < prices[1]) 
            return prices[1] - prices[0];                   // 特殊情况处理
        vector<vector<int>> dp(len, vector<int>(3, 0));
        for(int j = 1; j <= 2 ; j++){
            int minValue = prices[0];
            for (int i = 1; i < len; i++){
                minValue = min(minValue , prices[i] - dp[i - 1][j - 1]);
                dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
            }
        }
        return dp[len - 1][2];
    }
};

LeetCode188. 买卖股票的最佳时机 IV

此题与上一题的不同点在于可以进行k次交易,但解题思路是一样的,只不过当k >= len/2时,可退化为无限次交易的问题。

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int len = prices.size();
        if(len <= 1) return 0;                      // 特殊情况处理
        if (k >= len/2){                            //当k >= len/2时,可退化为无限次交易的问题
            int res = 0;
            for (int i = 1; i < len; i ++){
                res += max(0, prices[i] - prices[i - 1]);
            }
            return res;
        }
        vector<vector<int>> dp(len, vector<int>(k + 1, 0));
        for(int j = 1; j <= k ; j++){
            int minValue = prices[0];
            for (int i = 1; i < len; i++){
                minValue = min(minValue , prices[i] - dp[i][j - 1]);
                dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
            }
        }
        return dp[len - 1][k];
    }
};

LeetCode312. 戳气球

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。

说明:

    你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
    0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

示例:

输入: [3,1,5,8]
输出: 167 
解释: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
     coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

状态表示: f[i][j] 表示戳破 (i, j) 之间所有气球的集合,属性(最大值)这题的状态表示很巧妙,用到了开区间

状态计算:设 i 和 j 之间最后一个被戳破的气球为 k,那此时 (i, k) 和 (k, j) 之间的气球已被戳破,最后 (i, j) 之间只剩下气球 k ,相邻的就是气球 i 和 j,当j − i >= 2时,f[i][j] = f[i][k] + f[k][j] + p[i]∗p[k]∗p[j]

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int length = nums.size();
        vector<int> tmp(length + 2, 1);     
        vector<vector<int>> dp(length + 2, vector<int>(length + 2, 0));
        for (int i = 1; i <= length; i++) 
            tmp[i] = nums[i - 1];           //[3,1,5,8]变成[1,3,1,5,8,1]
        for (int len = 1; len <= length + 2; len ++) {          //len的范围[3,1,5,8,1]
            for (int i = 0; i + len - 1 <= length + 1; i++) {   //i为左边界范围[1,3,1,5,8]
                int j = i + len - 1;                            //j为右边界
                if (len == 1 || len == 2) dp[i][j] = 0;         //意味着右边界至少比左边界大2
                for (int k = i + 1; k <= j - 1; k++) {          
                    //k为最后戳破的气球,此时 (i,k) 和 (k,j)之间的气球已被戳破
                    //最后 (i,j) 之间只剩下气球 k 
                    dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + tmp[i] * tmp[k] * tmp[j]);
                }
            }
        }
        return dp[0][length + 1];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值