股票市场交易策略优化
题目描述
小R近期表现出色,公司决定以股票的形式给予奖励,并允许他在市场上进行交易以最大化收益。给定一个数组,数组中的第 i 个元素代表第 i 天的股票价格。小R需要设计一个算法来实现最大利润。
股票交易规则如下:
- 小R可以多次买卖股票,但在买入新的股票前必须卖出之前的股票。
- 每次卖出股票后存在一天的冷冻期,在冷冻期内小R不能购买股票。
你的任务是帮助小R计算出在遵守交易规则的情况下能够获得的最大利润。
题目解析
本题个人认为是一个非常好的动态规划例题,接下来我将从力扣上一道相似例题开始,逐步破解上面这道题,并总结解决这类题的共通方法,让大家都能在做动态规划类题目时有所思路,不至于无从下手。
当我一看到题目,就想到力扣上有一道十分相似的题目 ,名字叫买股票的最佳时机。它与这道题的要求相同,都是求能获得的最大利润,唯一不同之处是本题要求卖完股票的下一天禁止再次购买,这个条件也正是本题的难点所在此处和破冰点。让我先来看看没有这个条件时我们可以怎么做,为了更好的解释,我们假定条件stocks = [1, 6, 2, 7, 13, 2, 8],并以此为基础画出以下图
由于只有一次买卖机会,所以可以列出以下状态转移方程 令price为例表中的股票价值
cost = min(cost, price),
profit = max(profit, price - cost)
思路解析
具体的代码思路是: 1、更新前 i 天的最低价格,即最低买入成本 cost。 2、更新前 i 天的最高利润 profit ,即选择「前 i−1 天最高利润 profit 」和「第 i 天卖出的最高利润 price - cost 」中的最大值。
当状态转移方程都成了了,这道题基本也就成了。具体代码如下
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int maxProfit(vector<int>& prices) {
if (prices.empty()) return 0; // 如果数组为空,返回 0
int cost = INT_MAX; // 初始化为一个较大的值
int profit = 0; // 初始化利润为 0
for (int price : prices) {
cost = min(cost, price); // 更新最低价格
profit = max(profit, price - cost); // 更新最大利润
}
return profit;
}
int main() {
// 示例测试
vector<int> prices = {7, 1, 5, 3, 6, 4};
cout << "最大利润: " << maxProfit(prices) << endl;
return 0;
}
相信看到这里的同学已经大概有点感觉了,通过列出状态转移方程,实现对数据的动态变化,从而得出结果。现在难度升级,当加上“卖完股票的下一天禁止再次购买”这个条件之后该如何列举状态转移方程呢?需要注意的是,力扣那道题只探讨一次买卖的结果,这里我们需要探讨多次购买的结果,所以做法会有些许区别,但总的思路大差不差。有了上一道题的引入,这道题我们同样可以列举两个数组,他们分别表示买和卖的两种状态,buy 和 sell。而buy[i]、sell[i]则代表第i天持有股票和不持有股票的最大利润。
具体代码思路是:1、更新第i天是买入操作的最大利润。2、更新第i天是卖出操作的最大利润。3、每一天都根据前一天的状态来更新当前的买入和卖出最大利润。具体状态转移方程代码如下
buy[i] = max(buy[i - 1], (i >= 2 ? sell[i - 2] : 0) - stocks[i]),
sell[i] = max(sell[i - 1], buy[i - 1] + stocks[i])
代码解析
代码解析:当第i天操作是 买 时,可以选择不买,即保持第i-1天买的状态。也可以选择买,由于一天冷静期的原因,所以再次购买时应该从sell[i-2]的状态买股票,中间至少相隔一天。当第i天是 卖 时,顾客可以选择不卖,同样保持第 i-1 天卖的状态。也可以选择卖出,卖出时,状态应该从buy[i-1]天的状态开始,因为题目规定,你要买股票,前一天必须有股票。
现在我们已经顺利推导出状态转移方程,那么代码实现就很简单了,具体代码如下
代码实现
public class Main {
public static int solution(int[] stocks) {
int n = stocks.length;
if (n == 0)
return 0;
int[] buy = new int[n];
int[] sell = new int[n];
// 初始化
buy[0] = -stocks[0];
sell[0] = 0;
// 状态转移
for (int i = 1; i < n; i++) {
buy[i] = Math.max(buy[i - 1], (i >= 2 ? sell[i - 2] : 0) - stocks[i]);
sell[i] = Math.max(sell[i - 1], buy[i - 1] + stocks[i]);
}
// 返回结果
return sell[n - 1];
}
public static void main(String[] args) {
// You can add more test cases here
System.out.println(solution(new int[] { 1, 2 }) == 1);
System.out.println(solution(new int[] { 2, 1 }) == 0);
System.out.println(solution(new int[] { 1, 2, 3, 0, 2 }) == 3);
System.out.println(solution(new int[] { 2, 3, 4, 5, 6, 7 }) == 5);
System.out.println(solution(new int[] { 1, 6, 2, 7, 13, 2, 8 }) == 12);
}
}
总结
相信看到这的同学可以感受到做动态规划这类题是有一定的规律的,而我本身也是是十分赞同这个观点的。在我做过这么多道动态规划类题目后,我也给自己总结出一套适用于绝大多数动态规划题目的方法,如果按照我这个方法来做,你会对动态规划类题目有更深的体会,以至于不会出现下不了手的情况。我的方法总结:
1、建立动归数组并确认其中的含义。这句话的意思是你必须确认你所建立的数组的索引以及里面包含值的含义,绝大多数的人题目做不下去就是卡在这一步,我最初也一样,但是这个是可以依靠刷题练出手感的。当你确认建立的动归数组的含义后,就可以进入下一步。
2、初始化数组。这一步决定了你的代码能否成功运行,有时你建立的数组需要人为初始化,以达到你推出的状态转移方程的使用要求。
3、最后一点也是最难的一点,推导状态转移方程。你需要通过题目确认有哪些状态,并尝试去建立状态之间的联系,最终推导出状态方程。虽然说起来很简单,但实际操作确是十分困难,这个也是需要依靠大量的刷题经验和学习别人代码的经验来帮助你快速建立状态转移方程。你需要确认是需要一维dp数组还是二维,以及最终结果的获取位置,虽然很复杂,但是当你这三点都做到之后,题目自然而然就解开了。
最后这个总结是本人花费许多时间总结出的经验之谈,对于一些大佬来说可能没用,但对于新手来说应该还是有帮助的。以上仅为个人观点,以上解析或见解如有不满敬请开喷。