买卖股票含手续费 图解题解
这道题到底在问什么
- 输入
- prices=[1,4,2,7,3,2,5,1,6], fee=2
- 输出
- 8
最优解:为什么这么做
一句话答案:LeetCode 714 买卖股票含手续费是两状态 DP:每天维护持股、空仓两态,空仓=max(空仓,持股+价−fee) 卖出才扣一次费、持股=max(持股,空仓−价),答案取空仓。时间 O(n)、空间 O(1)。
反复买卖还要交手续费,什么时候动手反而亏钱
给数组 prices,prices[i] 是第 i 天股价,另有固定手续费 fee,卖出扣一笔。可反复买卖,但同时最多持一股,想再买得先卖,求最大利润。题面 prices=[1,4,2,7,3,2,5,1,6]、fee=2,答案 8。难在手续费:差价太小时赚的不够交费,动手反亏。
把每天买卖不动都枚举一遍,为什么撑不住
暴力是每天买、卖、不动三选一全枚举挑最高。可 n 天就是 3 的 n 次方量级,稍多就跑不完。决定明天怎么选的只有此刻手里有没有股,把它记下来、每种只算一次,指数枚举就压成一趟线性递推(拿前面算好的值推后面)。
持股、空仓两个状态,各自记的是账上多少钱
每天收盘只两种状态:持股或空仓。两个变量记这两态的最大现金——hold 记收盘持股时最多剩多少,cash 记收盘空仓时最多剩多少。hold 通常为负,买入先垫钱,第 0 天以 1 元买那股账上成 -1。开局 hold=-prices[0]、cash=0。
两态怎么互相转移,手续费为什么只在卖出扣一次
每态两条来路取更划算的。持股要么接着拿旧 hold,要么今天买——昨天空仓掏 prices[i],取 hold=max(hold, cash-prices[i])。
空仓要么接着空旧 cash,要么今天卖——昨天持股以 prices[i] 卖出、加回股价再扣 fee,取 cash=max(cash, hold+prices[i]-fee)。
手续费只在卖出扣一次:一趟买卖收一次费,买入只垫股价,卖出才结清,两头都扣就交两回。
拿题面 fee=2 的示例逐天填两行
开局第 0 天价 1,hold=-1、cash=0。第 1 天价 4:hold=max(-1,0-4)=-1,cash=max(0,-1+4-2)=1。第 2 天价 2:hold=max(-1,1-2)=-1,cash=max(1,-1+2-2)=1。第 3 天价 7:hold=max(-1,1-7)=-1,cash=max(1,-1+7-2)=4。第 4 天价 3:hold=max(-1,4-3)=1(4 即第 3 天的 cash),cash=max(4,1+3-2)=4。第 5 天价 2:hold=max(1,4-2)=2,cash=max(4,2+2-2)=4。第 6 天价 5:hold=max(2,4-5)=2,cash=max(4,2+5-2)=5。第 7 天价 1:hold=max(2,5-1)=4,cash=max(5,4+1-2)=5。第 8 天价 6:hold=max(4,5-6)=4,cash=max(5,4+6-2)=8。空仓 cash=8 即最大利润。
答案为什么取空仓、hold 初值又设成负数,复杂度多少
每天两次取大,一遍 O(n);只留 hold、cash 两变量滚动,空间 O(1)。
买入别扣费——fee 只在卖出减这一次,两头都扣等于一趟交两回。hold 初值也别写成 0:第 0 天持股账上已垫掉买入价,初值得是 -prices[0],写 0 相当于白得一股。最后返回 cash 别返回 hold,收盘还攥着股票的钱没落袋,永远不会更优。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这「两态」,下面每天都在它们之间切换。
- 4顶行是每天股价(固定)。下面两行:持股 / 空仓,待逐天填。
- 5第 0 天开局:空仓就是 0;若买入,现金先垫掉股价,变成负数。
- 6填入:持股 hold[0]=-1(买了 1 元那股),空仓 cash[0]=0。
- 7第 1 天(股价 4):持股两条路——接着拿(-1) 或 今天买(-4);空仓两条路——接着空(0) 或 今天卖(1,卖出已扣手续费 2)。
- 8取较大的:hold[1]=-1(持股没变(不如继续拿)),cash[1]=1(今天卖出更划算)。
- 9第 2 天(股价 2):持股两条路——接着拿(-1) 或 今天买(-1);空仓两条路——接着空(1) 或 今天卖(-1,卖出已扣手续费 2)。
- 10取较大的:hold[2]=-1(持股没变(不如继续拿)),cash[2]=1(空仓没变(这天卖了反而亏,不如不卖))。
- 11第 3 天(股价 7):持股两条路——接着拿(-1) 或 今天买(-6);空仓两条路——接着空(1) 或 今天卖(4,卖出已扣手续费 2)。
- 12取较大的:hold[3]=-1(持股没变(不如继续拿)),cash[3]=4(今天卖出更划算)。
- 13第 4 天(股价 3):持股两条路——接着拿(-1) 或 今天买(1);空仓两条路——接着空(4) 或 今天卖(0,卖出已扣手续费 2)。
- 14取较大的:hold[4]=1(今天买入更划算),cash[4]=4(空仓没变(这天卖了反而亏,不如不卖))。
- 15第 5 天(股价 2):持股两条路——接着拿(1) 或 今天买(2);空仓两条路——接着空(4) 或 今天卖(1,卖出已扣手续费 2)。
- 16取较大的:hold[5]=2(今天买入更划算),cash[5]=4(空仓没变(这天卖了反而亏,不如不卖))。
- 17第 6 天(股价 5):持股两条路——接着拿(2) 或 今天买(-1);空仓两条路——接着空(4) 或 今天卖(5,卖出已扣手续费 2)。
- 18取较大的:hold[6]=2(持股没变(不如继续拿)),cash[6]=5(今天卖出更划算)。
- 19第 7 天(股价 1):持股两条路——接着拿(2) 或 今天买(4);空仓两条路——接着空(5) 或 今天卖(1,卖出已扣手续费 2)。
- 20取较大的:hold[7]=4(今天买入更划算),cash[7]=5(空仓没变(这天卖了反而亏,不如不卖))。
- 21第 8 天(股价 6):持股两条路——接着拿(4) 或 今天买(-1);空仓两条路——接着空(5) 或 今天卖(8,卖出已扣手续费 2)。
- 22取较大的:hold[8]=4(持股没变(不如继续拿)),cash[8]=8(今天卖出更划算)。
- 23最右的空仓 cash[8]=8 就是最大利润——手里别留股,全部落袋。
⚠️ 容易写错的地方
✗ 错:买入时也扣手续费
✓ 对:手续费只在卖出时扣一次
一次完整买卖只算一笔费用
✗ 错:hold 初值写 0
✓ 对:hold[0]=-prices[0]
第 0 天若持股,现金已垫掉买入价
✗ 错:答案取 hold
✓ 对:答案是 cash[n-1]
结束时还攥着股票等于没落袋,一定不优
完整代码(Python / C++ / Java)
Python
def maxProfit(prices, fee):
hold, cash = -prices[0], 0
for p in prices[1:]:
hold = max(hold, cash - p)
cash = max(cash, hold + p - fee)
return cashC++
int maxProfit(vector<int>& prices, int fee){
long hold = -prices[0], cash = 0;
for(size_t i=1;i<prices.size();i++){
hold = max(hold, cash - prices[i]);
cash = max(cash, hold + prices[i] - fee);
}
return cash;
}Java
int maxProfit(int[] prices, int fee){
int hold = -prices[0], cash = 0;
for(int i = 1; i < prices.length; i++){
hold = Math.max(hold, cash - prices[i]);
cash = Math.max(cash, hold + prices[i] - fee);
}
return cash;
}复杂度
时间
O(n)
一遍线性递推
空间
O(1)
只留 hold/cash 两个状态
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 买卖股票含手续费 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不含手续费的版本 LeetCode 122 怎么从这题改过来?+
把卖出那条来路里的 -fee 去掉即可:cash=max(cash, hold+prices[i]),持股那条一字不改。两题都是持股、空仓两态滚动,122 因为不收费,任何一天只要今天价比昨天高就值得「昨买今卖」;本题则要差价先盖过 fee 才划算,否则宁可不动。骨架完全一样,本题只是每次卖出多扣一道费。
为什么最后返回 cash 而不是 hold?+
hold 表示收盘时手里还攥着一股,那股的钱还没变现,是账面浮盈、不是能拿走的现金。任何持股状态,只要在最后一天按当天价卖掉(哪怕倒贴手续费),结果要么更优要么持平,绝不会更差,所以空仓终态一定不比持股差。cash 记的才是真正落袋的最大利润,返回它。
手续费算在卖出还是买入,会影响最终答案吗?+
不影响最大利润,只影响每天的中间值。一趟买卖收一笔固定费,记在买入还是卖出都行,只要一趟别收两次。若改成买入扣、卖出不扣,转移就变成 hold=max(hold, cash-prices[i]-fee)、cash=max(cash, hold+prices[i]),最终答案不变。真正的坑是买卖两头都扣,那会把一趟的费重复算两回,利润算少。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 买卖股票含手续费 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。