leetcode-代码随想录-动态规划-188买卖股票的最佳时机Ⅳ

题目

题目链接:188. 买卖股票的最佳时机 IV - 力扣(LeetCode)

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

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

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
输入:k = 2, prices = [3,2,6,5,0,3]
输出:7
解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
     随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        
    }
};
思路

121. 买卖股票的最佳时机 - 力扣(LeetCode)122. 买卖股票的最佳时机 II - 力扣(LeetCode)123. 买卖股票的最佳时机 III - 力扣(LeetCode)的区别:

  • 121题:只能交易一次,买入时之前没有利润,所以是 -prices[i]
  • 122题:可以多次交易,买入时可以用之前交易获得的利润,所以是 dp[i-1][1] - prices[i]
  • 123题:最多完成两笔交易
  • 188题:最多可以完成 k 笔交易

本题可以说是123题的进阶版,要求最多完成k笔交易。

1. DP数组含义

123. 买卖股票的最佳时机 III - 力扣(LeetCode)题中,采用标准二维 DP,需要两个维度:

  • 时间维度:第 i
  • 状态维度:第 k 次持有或不持有股票(123题中 k = 2,本题 k 不固定)

dp[i][j]中 i 表示第 i 天,j 为 [0-4]五个状态,dp[i][j]表示第 i 天状态 j 的最大利润:

  • j = 0:没有操作
  • j = 1:第一次持有股票
  • j = 2:第一次不持有股票
  • j = 3:第二次持有股票
  • j = 4:第二次不持有股票

  • 所以:
  • dp[i][1]:第 i 天,第一次持有股票,能获得的最大利润
  • dp[i][2]:第 i 天,第一次不持有股票,能获得的最大利润

可以看出,除 j = 0 以外,当 j 为奇数时就是持有,j 为偶数时就是不持有。

题目要求是至多有K笔交易,那么j的范围就定义为 2 * k + 1 就可以了。

2. 递推公式

状态j = 0:没有操作

所以:dp[i][0] = dp[i-1][0];

状态j = 1:第一次持有股票

持有股票有两种可能:

  • i - 1 天就持有,第 i 天继续持有:dp[i-1][1]
  • i 天买入:可以多次交易, 买入时可以使用之前交易获得的利润,相当于之前的总利润 - 当前股价,即 dp[i - 1][0] - prices[i]

递推公式:dp[i][1] = max(dp[i-1][1], dp[i - 1][0] - prices[i])

状态j = 2:第一次不持有股票

不持有股票有两种可能:

  • i - 1 天就不持有,第 i 天继续不持有:dp[i-1][2]
  • i 天卖出:卖出前的状态是持有股票, 所以是 dp[i-1][1] + prices[i]

递推公式:dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])

状态j = 3:第二次持有股票

持有股票有两种可能:

  • i - 1 天就持有,第 i 天继续持有:dp[i-1][3]
  • i 天买入:可以多次交易, 买入时可以使用之前交易获得的利润,相当于之前的总利润 - 当前股价,即 dp[i - 1][2] - prices[i]

递推公式:dp[i][3] = max(dp[i-1][3], dp[i - 1][2] - prices[i])

状态j = 4:第二次不持有股票

不持有股票有两种可能:

  • i - 1 天就不持有,第 i 天继续不持有:dp[i-1][4]
  • i 天卖出:卖出前的状态是持有股票, 所以是 dp[i-1][3] + prices[i]

递推公式:dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])

根据以上可以推导出,递推公式:

for(int j = 0; j < 2 * k - 1; j += 2){
	dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
	dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

3. DP数组初始化

  • dp[0][0]:没有操作,所以 dp[0][0] = 0
  • dp[0][1]:第 0 天第一次持有股票, 所以 dp[0][1] = -prices[0]
  • dp[0][2]:第 0 天第一次不持有股票, 所以 dp[0][2] = 0;(即当天买入,当天卖出)
  • dp[0][3]:第 0 天第二次持有股票, 所以 dp[0][3] = -prices[0];(第 0 天第一次买入,第一次卖出,然后再买入一次(第二次买入))
  • dp[0][4]:第 0 天第二次不持有股票, 所以 dp[0][4] = 0

可以推出 dp[0][j]j 为奇数时都初始化为 -prices[0]

for(int j = 1; j < 2 * k; j += 2){
	dp[0][j] = -prices[0];
}

4. 确定遍历顺序

从递推公式可以看出 dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历。

5. 打印DP数组

以输入[1,2,3,4,5],k=2为例。
image.png
最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解。

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <sstream>
#include <cmath>
using namespace std;

class Solution {
public:
    int maxProfit(int k, vector<int>& prices)  {
        int len = prices.size();
        if(len == 0) return 0;
        
        // 标准二维 dp
        vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
        // dp[0][0] = 0;  // 无任何操作
        // dp[0][1] = -prices[0];   // 第 0 天、第一次持有股票
        // dp[0][2] = 0;   // 第 0 天、第一次不持有股票
        // dp[0][3] = -prices[0];   // 第 0 天、第二次持有股票
        // dp[0][4] = 0;   // 第 0 天、第二次不持有股票

        for(int j = 1; j < 2 * k; j += 2){
            dp[0][j] = -prices[0];
        }

        for(int i = 1; i < len; i++){
            for(int j = 0; j < 2 * k; j += 2){
                dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); // 持有股票
                dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]); // 不持有股票
            
            }
        }

        for(int i = 0; i < len; i++){
            for(int j = 0; j < 2 * k + 1; j++){
                cout << dp[i][j] << " ";
            }
            cout << endl;
        }
        cout << endl;
        return dp[len - 1][2 * k];
        
    }
};

vector<int> inputvector(){
    string line;
    getline(cin, line);
    istringstream iss(line);
    vector<int> vec;
    int num;

    while(iss >> num) {
        vec.push_back(num);
    }
    return vec;
}

int main(){
    

    cout << "prices: ";
    vector<int> prices = inputvector();

    cout << "k: ";
    int k;
    cin >> k;

    Solution obj;
    int res = obj.maxProfit(k, prices);

    cout << "res: " << res << endl;
    
    return 0;
}
prices: 1 2 3 4 5
k: 2
0 -1 0 -1 0 
0 -1 1 -1 1
0 -1 2 -1 2
0 -1 3 -1 3
0 -1 4 -1 4

res: 4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值