动态规划——股票问题全解

1 篇文章 0 订阅

引入

股票问题是一类动态问题,我们需要对其状态进行判定分析来得出答案

但其实,我们只需要抓住两个点,持有和不持有,在这两种状态下分析问题会简单清晰许多

下面将会对各个问题进行分析讲解,来解释什么是持有和不持有状态,并在分析后得到题目的解答​​​​​​​买卖股票的最佳时机


1、​​​​​​​买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

分析:

我们了解到这道题,只能够购买和卖出一次,那么,

持有的状态只有两种情况:1.之前就持有  2.今天刚买入

不持有也有两种情况:1.昨天就不持有 2.今天刚出售

1.dp空间

通过以上的分析我们可以知道,解决这个问题我们需要了解:

1.今天的价格  2.昨天的状态

所以,我们需要两对空间,保存两对持有与不持有状态

即一个2*2大小的数组

vector<vector<int>> dp(2, vector<int>(2));

2.初始状态

其中dp[i][0] 代表第i天的持有状态下的资金数

        dp[i][1] 代表第i天的不持有状态下的资金数

由此我们也可以知道,第一天的持有状态只能是进行买入 即 -prices[0]

                                                   不持有状态只能是0

所以初始状态我们也有了

dp[0][0] = -prices[0];
dp[0][1] = 0;

3.递推公式

由刚开始的分析其实我们已经知道了递推公式,现在只需要进行代码实现即可:

先使用伪代码描述

当天持有股票的最大金额 = max(昨天就持有时的资金数,今天刚买入后所拥有的资金)

当天不持有股票的最大金额 = max(昨天就不持有时的资金数,今天刚卖出后所拥有的资金)

代码实现

其中的取模运算是为了获得之前的状态而不用进行数据移动

//因为只进行一次买入卖出,所以当前买入一定是 0 - prices[i]
dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);

4.题解代码 

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(2)); 
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
        }
        return dp[(len - 1) % 2][1];
    }
};


2、买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

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

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

示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

分析:

这道题与上一道题的最大差别就是,可进行多次买入卖出,那么如上一道题相同,先分析两种状态

持有的状态只有两种情况:1.之前就持有  2.今天买入

不持有也有两种情况:1.昨天就不持有 2.今天出售

1.dp空间

通过以上的分析我们可以知道,解决这个问题我们需要了解:

1.今天的价格  2.昨天的状态

所以,我们需要两对空间,保存两对持有与不持有状态

即一个2*2大小的数组

vector<vector<int>> dp(2, vector<int>(2));

2.初始状态

其中dp[i][0] 代表第i天的持有状态下的资金数

        dp[i][1] 代表第i天的不持有状态下的资金数

由此我们也可以知道,第一天的持有状态只能是进行买入 即 -prices[0]

                                                   不持有状态只能是0

所以初始状态与上一道题相同

dp[0][0] = -prices[0];
dp[0][1] = 0;

 

3.递推公式

使用伪代码描述

当天持有股票的最大金额 = max(昨天就持有时的资金数,今天买入后所拥有的资金)

当天不持有股票的最大金额 = max(昨天就不持有时的资金数,今天刚卖出后所拥有的资金)

代码实现

其中的取模运算是为了获得之前的状态而不用进行数据移动

注意,因为需要进行多次买入卖出,所以 持有股票状态中 今天买入后所拥有的资金 需要由之前的状态转换而来,这是与前一道题不同的地方

//注意,这里的今天买入状态,是由昨天未持有状态推导而来
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);

4.题解代码  

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(2));
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
        }
        return dp[(len - 1) % 2][1];
    }
};

3、买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

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

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

示例 2: 输入:prices = [1,2,3,4,5] 输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3: 输入:prices = [7,6,4,3,1] 输出:0 解释:在这个情况下, 没有交易完成, 所以最大利润为0。

示例 4: 输入:prices = [1] 输出:0

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^5

分析:

这道题与前题的差别在于,只能进行两笔交易,即两次买入卖出过程,那么如上一道题相同,先分析两笔交易各自的状态

第一笔:

持有的状态只有两种情况:1.之前就持有  2.今天刚买入

不持有也有两种情况:1.昨天就不持有 2.今天刚出售

但是情况不同的是,第二笔的交易,需要根据第一笔交易的基础上进行

所以第二笔:

持有的状态只有两种情况:1.之前就持有  2.今天刚买入(但需要加上前一笔交易完成的状态)

不持有也有两种情况:1.昨天就不持有 2.今天刚出售

1.dp空间

通过以上的分析我们可以知道,解决这个问题我们需要了解:

1.第一笔交易的持有与不持有状态  2.第一笔交易的持有与不持有状态 

所以,我们需要两对空间,保存两笔交易的状态

即一个2*2大小的数组

vector<vector<int>> dp(2, vector<int>(2, 0));

 

2.初始状态

其中dp[i][0] 代表第i天的持有状态下的资金数

        dp[i][1] 代表第i天的不持有状态下的资金数

由此我们也可以知道,第一天第一笔的持有状态只能是进行买入 即 -prices[0]

                                                   不持有状态只能是0

又因为第二笔的状态根据第一笔的状态转换

所以第二笔的持有状态是第一笔的不持有状态-当前价格 即 0-prices[0]

                                                   不持有状态只能是0

所以初始状态如下

dp[0][0] = -prices[0];
dp[1][0] = -prices[0];

 

3.递推公式

使用伪代码描述

第一笔交易持有股票的最大金额 = max(昨天就持有时的资金数,今天买入后所拥有的资金)

第一笔交易不持有股票的最大金额 = max(昨天就不持有时的资金数,今天刚卖出后所拥有的资金)

第二笔交易持有股票的最大金额 = max(昨天就持有时的资金数,今天买入后所拥有的资金)

第二笔交易不持有股票的最大金额 = max(昨天就不持有时的资金数,今天刚卖出后所拥有的资金)

代码实现

至于为什么不保存第一笔之前的状态了,大致可以理解为,两天合起来才是需要的完整的交易,即第一笔就保存了第二笔之前的数据,第一笔直接交易即可

dp[0][0] = std::max(dp[0][0], 0 - prices[i]);
dp[0][1] = std::max(dp[0][1], prices[i] + dp[0][0]);
dp[1][0] = std::max(dp[1][0], dp[0][1] - prices[i]);
dp[1][1] = std::max(dp[1][1], prices[i] + dp[1][0]);

 4.题解代码  

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

4、买卖股票的最佳时机 IV

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

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

示例 1: 输入:k = 2, prices = [2,4,1] 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 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 。

提示:

  • 0 <= k <= 100
  • 0 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000

分析:

其实这道题与前一道题是相同的题目,只是将交易次数修改为任意次数

只需要与上道题进行相同的模式即可

这里就直接摆出代码:

但需要注意的是,第一笔交易买入时肯定是0 - prices[i]的价值,所以在此处使用多开一层数组,来保持动态的首次为0的概念,即代码中的

dp[j - 1][1] - prices[i]

class Solution {
public:
	int maxProfit(int k, vector<int>& prices) {
		int n = prices.size();
		vector<vector<int>> dp(k + 1, vector<int>(2, 0));
		for (int i = 1; i <= k; ++i) {
			dp[i][0] = -prices[0];
		}
		for (int i = 1; i < n; ++i) {
			for (int j = 1; j <= k; ++j) {
				dp[j][0] = std::max(dp[j][0], dp[j - 1][1] - prices[i]);
				dp[j][1] = std::max(dp[j][1], prices[i] + dp[j][0]);
			}
		}
		return dp[k][1];
	}
};

5、最佳买卖股票时机含冷冻期

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

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

示例:

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

分析:

这道题与前面的题就不一样了,因为这道题多出了一种状态,即冰冻期的状态

让我们对三种状态进行分析

持有:1.前一天就持有 2.今天刚持有

不持有:1.前一天就不持有 2.今天是冷冻期

冷冻期:今天卖出,进入冷冻期

可能对这个分割会有疑虑,为什么冷冻期的状态不算冷冻期当天,而是算其交易完成的那天

因为我们一直都有在进行转换的判断:

比如:持有的状态,是今天由不持有转向持有

           不持有的状态,是由持有转向不持有

所以冷冻期的概念:是由非冷冻期转向冷冻期

2.初始状态

初始状态不涉及冷冻期,所以初始状态没有变化

3.递推公式

使用伪代码描述

交易持有股票的最大金额 = max(昨天就持有时的资金数,今天买入后所拥有的资金)

交易不持有股票的最大金额 = max(昨天就不持有时的资金数,今天是冷冻期的资金)

冷冻期的最大金额 = 今天卖出后的资金

代码实现

dp[i][0] = std::max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = std::max(dp[i - 1][2], dp[i - 1][1]);
dp[i][2] = dp[i - 1][0] + prices[i];

  

4.题解代码  

注意:交易结束的最大金额,是由两个方面来决定:不持有状态和冷冻期状态

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

6、买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1: 输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 输出: 8

解释: 能够达到的最大利润: 在此处买入 prices[0] = 1 在此处卖出 prices[3] = 8 在此处买入 prices[4] = 4 在此处卖出 prices[5] = 9 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

注意:

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

分析:

这个题就很简单了,只是在第二题的基础上加了个交易费而已

就直接放出代码即可:

此处数组的层数改为n,但依旧是之前保存前一天的概念,没有区别

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值