题目
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
1 | 输入: [7,1,5,3,6,4] |
示例 2:
1 | 输入: [7,6,4,3,1] |
解法一:暴力解法
暴力求解,不解释。
枚举所有发生一次交易的股价差
1 | public int maxProfit(int[] prices) { |
解法二:动态规划
题目只问最大利润,没有问这几天具体哪一天买、哪一天卖,因此可以考虑使用 动态规划 的方法来解决。
买卖股票有约束,根据题目意思,有以下两个约束条件:
- 条件 1:你不能在买入股票前卖出股票;
- 条件 2:最多只允许完成一笔交易。
因此 当天是否持股 是一个很重要的因素,而当前是否持股和昨天是否持股有关系,为此我们需要把 是否持股 设计到状态数组中。
状态定义:
dp[i] [j]
:下标为 i
这一天结束的时候,手上持股状态为 j 时,我们持有的现金数。
- j = 0,表示当前不持股;
- j = 1,表示当前持股。
注意:这个状态具有前缀性质,下标为 i 的这一天的计算结果包含了区间 [0, i] 所有的信息,因此最后输出 dp[len - 1] [0]。
说明:
- 使用「现金数」这个说法主要是为了体现 买入股票手上的现金数减少,卖出股票手上的现金数增加 这个事实;
- 「现金数」等价于题目中说的「利润」,即先买入这只股票,后买入这只股票的差价;
- 因此在刚开始的时候,我们的手上肯定是有一定现金数能够买入这只股票,即刚开始的时候现金数肯定不为 00,但是写代码的时候可以设置为 0。极端情况下(股价数组为 [5, 4, 3, 2, 1]),此时不发生交易是最好的(这一点是补充说明,限于我的表达,希望不要给大家造成迷惑)。
推导状态转移方程:
dp[i] [0]:规定了今天不持股,有以下两种情况:
昨天不持股,今天什么都不做;
昨天持股,今天卖出股票(现金数增加),
dp[i] [1]:规定了今天持股,有以下两种情况:昨天持股,今天什么都不做(现金数增加);
昨天不持股,今天买入股票(注意:只允许交易一次,因此手上的现金数就是当天的股价的相反数)。
状态转移方程请见 参考代码 2。
知识点:
- 多阶段决策问题:动态规划常常用于求解多阶段决策问题;
- 无后效性:每一天是否持股设计成状态变量的一维。状态设置具体,推导状态转移方程方便
1 | public class Solution { |
解法三: 滚动数组优化
分析
java
1 | public class Solution { |
% 2
还可以写成 & 1
,这里为了保证可读性,选用 % 2
。
解法四: 空间优化
空间优化只看状态转移方程。
状态转移方程里下标为 i 的行只参考下标为 i - 1 的行(即只参考上一行),并且:
- 下标为 i 的行并且状态为 0 的行参考了上一行状态为 0 和 1 的行;
- 下标为 i 的行并且状态为 1 的行只参考了上一行状态为 1 的行。
1 | public class Solution { |