最近审阅:2026-10 · 先读:回溯与 DP

这是一个常见的算法练习,不是投资建议:每天给一个价格,一次最多持有 1 份,卖出后第二天不能买入。问最后不持有时,最多能赚多少。

如果只记“到昨天为止最大收益”,你不知道那个收益来自刚卖出,还是已经休息了一天。两种情况的钱可能一样,今天允许的动作却不同。这就是需要额外状态的原因。

用明天能做什么,区分今天的状态

在每一天结束时,分成三类:

状态 含义 明天可以做什么
holding 持有 1 份;记录扣除买入支出后的净收益 继续持有或卖出
sold 今天刚卖出 必须休息,不能买
resting 不持有,也不是今天刚卖出 休息或买入

初始在任何交易之前:resting=0,另两个为负无穷,表示不可能。不能把 sold 也初始化成一个真实的成交记录。

resting ── buy ─────→ holding
holding ── sell ────→ sold
sold ──── cooldown → resting
resting ── stay ────→ resting
holding ── hold ────→ holding

箭头是跨一天的转移,不能在一天里沿多条箭头走。图的目的只是说明哪些状态相连;真正更新看下面的等式。

转移只读昨天,不能偷用今天的新值

第 $t$ 天价格为 $p_t$。用 $H,S,R$ 分别表示三种状态的最好收益:

$$ \begin{aligned} H_t&=\max(H_{t-1},R_{t-1}-p_t),\\ S_t&=H_{t-1}+p_t,\\ R_t&=\max(R_{t-1},S_{t-1}). \end{aligned} $$

持有可以来自继续持有,或昨天结束时处于 resting、今天买入;不能从昨天的 sold 买入。休息则可以来自继续休息,或昨天卖出后度过冷却日。

状态为什么足够?同一天、同一种状态下,未来可选动作相同,所以收益较小的历史不会比收益较大的更好。保留每种状态的最大值就够了。如果增加交易次数上限,这个论证要把“还剩几次”也放进状态。

手走一遍,比记三个变量有效

价格 [1,2,3,0,2]:

时间 holding sold resting
开始前 −∞ −∞ 0
第 1 天,1 −1 −∞ 0
第 2 天,2 −1 1 0
第 3 天,3 −1 2 1
第 4 天,0 1 −1 2
第 5 天,2 1 3 2
动画 三个状态,每天只读昨天

这张图需要启用 JavaScript。

答案是 max(sold,resting)=3。可行路径是 1 买、2 卖、3 冷却、0 买、2 卖。第 4 天 holding=1 不代表“手里现金且没有资产”:它是扣除当前持仓成本后的净收益,不能直接当最后答案。

def cooldown_profit(prices):
    holding = float("-inf")
    sold = float("-inf")
    resting = 0
    for price in prices:
        holding, sold, resting = (
            max(holding, resting - price),
            holding + price,
            max(resting, sold),
        )
    return max(resting, sold)

Python 的多重赋值(元组解包)会先算完右边,再写左边,因此读的都是昨天。逐行覆盖三个变量,可能让后面的计算误读今天的新状态。输入约定是有限、非负价格;空列表返回 0。时间 $O(n)$,额外空间 $O(1)$;返回收益,不保留交易路径。

约束变了,状态或转移也要变

新条件 改哪里 不能假定什么
没有冷却日 合并不持有状态,允许卖出后的下一天买 旧三状态不是必须
每次交易收费 在买入或卖出时扣一次 不能两处都扣
最多交易 2 次 增加剩余次数或已完成次数 同样的钱不代表同样的后续机会
冷却多天 记录还要等多久,或用相应历史状态 不能只把某个常数改大
必须输出操作路径 保存每步来自哪个状态 滚动三个数无法恢复完整历史

这是确定性序列上的 DP,不是强化学习(reinforcement learning, RL):整条价格序列是题目输入,转移规则已知,没有学习策略的过程。它与 Bellman 思想有关,但别因为出现“状态”和“奖励(reward)”就叫它 RL。

检查时,专挑能破坏规则的例子

[1,2,3,0,2] 在没有冷却限制时答案是 4,有冷却时是 3,这能抓出遗漏约束的实现。全下降、只有一天、重复价格也要检查。

代码在 deeper_patterns.py。测试对短序列枚举所有合法买卖与等待动作,和这三个状态的结果比较;不是拿同一条递推式(recurrence relation)抄一遍当“独立答案”。公共练习可用 Stock with Cooldown,重点是学习如何补上影响未来选择的状态。

本页术语 3
强化学习 reinforcement learning, RL
根据行动带来的回报调整策略,学习怎样作出更好的选择
奖励 reward
RL 里指导策略更新的标量信号
递推式 recurrence relation
说明一个子问题的答案怎样由其他子问题的答案得到