【Leetcode刷题篇/面试篇】经典动态规划-买卖股票问题总结汇总

本文围绕动态规划解决买卖股票问题展开,涵盖一次买卖、多次买卖(含冷却期、手续费)、最多两次交易、最多K次交易等情况。针对不同情形给出示例和解题思路,通过维护变量、构建状态转移方程来计算最大利润。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

经典动态规划-买卖股票问题

一、一次买卖股票-121. 买卖股票的最佳时机

Leetcode121 剑指offer63

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

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

示例 1:

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

示例 2:

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

解题思路:维护一个变量存储当前天之前的最小股票价,计算维护最大利润即可。

class Solution {
    public int maxProfit(int[] prices) {
        // 判断数组
        if(prices==null|| prices.length==0 || prices.length==1){
            return 0;
        }
        int profit = 0;
        int min_prices = prices[0];
        for(int i=1;i<prices.length;i++){
            min_prices = Math.min(prices[i],min_prices);
            profit = Math.max(profit,prices[i]-min_prices);
        }
        return profit;
    }
}

二、多次买卖股票(不能同时参与多笔交易)-122. 买卖股票的最佳时机 II

Leetcode122

给定一个数组,它的第 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。

解题思路:考虑到【不能同时参与多笔交易】,因此每天交易结束后只可能存在手里有一只股票或者没有股票的状态。

dp0:没有股票的状态

dp1:有股票的状态

股票转移的方程:

dp0 Math.max(前一天没有股票的,前一天有股票卖掉了)

dp1 Math.max(前一天有股票,前一天没有股票买了一个)

class Solution {
    public int maxProfit(int[] prices) {
        int dp0 = 0;
        int dp1 = -prices[0];
        for(int i=1;i<prices.length;i++) {
            // 转移方程
            int newdp0 = Math.max(dp0, dp1+prices[i]);
            int newdp1 = Math.max(dp1, dp0-prices[i]);
            dp0 = newdp0;
            dp1 = newdp1;
        }
        return dp0;
    }
}

三、多次买卖股票(不能同时参与多笔交易)且包含冷却期-309. 最佳买卖股票时机含冷冻期

Leetcode309

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

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

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

示例:

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

解题思路:有三种不同的状态:

  • 持有一支股票; 初始化-price[0]
  • 不持有股票,并且处于冷冻期 初始化0
  • 不持有股票,并且不处于冷冻期 初始化0

对于转移方程来说:

  • 持有一只股票

    持有的这一只股票可能是i-1天就已经持有了;也可能是第i天买入的,那么i-1天就不能持有股票且不处于冷冻期,那么

    dp0= Math.max(前一天就持有了,前一天没有持有且不处于冷冻期 - 买来的)

  • 不持有一只股票且处于冷冻期

    持有股票且处于冷冻期表明今天卖出了,那么前一天必须持有股票 + 卖出的

    dp1=(前一天持有的股票dp0 +卖掉了)

  • 不持有一只股票且不处于冷冻期

    不持有股票且不处于冷冻期说明当天没操作,看前一天持有股票;

    看前一天持有卖出股票

    dp2=Math.min(前一天就是未持有冷冻dp1,前一天持有未冷冻期dp12)

最终结果就是看不持有股票中的最大值

class Solution {
	    public int maxProfit(int[] prices) {
            if (prices.length == 0) {
                return 0;
            }
	    	// 三个状态 
	    	// 持有
	    	int dp0 = -prices[0];
	    	// 未持有冷冻期
	    	int dp1 = 0;
	    	// 未持有不冷冻期
	    	int dp2 = 0;
	    	for(int i=1;i<prices.length;i++) {
	    		// dp0的转移方程 前一天就持有了 前一天未持有不冷冻期买了
	    		int newdp0 = Math.max(dp0, dp2-prices[i]);
	    		// dp1的转移方程  未持有冷冻期今天刚卖了
	    		int newdp1 = dp0+prices[i];
	    		// dp2的转移方程 今天什么也没有操作 看前一天未持有冷冻 看前一天未持有未冷冻
	    		int newdp2 = Math.max(dp1, dp2);
	    		dp0 = newdp0;
	    		dp1 = newdp1;
	    		dp2 = newdp2;
	    	}
	    	return Math.max(dp1, dp2);
	    }
	}

四、多次买卖股票(不能同时参与多笔交易)且包含手续费-714. 买卖股票的最佳时机含手续费

Leetcode714

给定一个整数数组 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.

解题思路:

两个变量:cash和hold分别是不持有股票的最大利润 持有股票的最大利润

转移方程:

cash不持有股票的最大利润:前一天本来就没有持有cash/hold 前一天持有了 今天卖了prices[i] 手续费fee

hold持有股票的最大利润:前一天就持有了hold/前一天没有持有 今天买入了cash - prices[i]

package com.lcz.leetcode;
// 买卖股票的最佳时机含手续费
public class Leetcode714 {
	class Solution {
	    public int maxProfit(int[] prices, int fee) {
	        if(prices.length==0){
	            return 0;
	        }
	        // 持有和未持有
		    	// 未持有
		    	int cash = 0;
		    	// 持有
		    	int hold = -prices[0];
		    	for(int i=1;i<prices.length;i++) {
		    		// 未持有转移方程 前一天就未持有 前一天持有今天卖了
		    		int newcash = Math.max(cash, hold+prices[i]-fee);
		    		// 持有转移方程 前一天持有 前一天未持有买了
		    		int newhold = Math.max(hold, cash-prices[i]);
		    		cash = newcash;
		    		hold = newhold;
		    	}
		    	return cash;
	    }
	}
}	

五、多次买卖股票(不能同时参与多笔交易)且最多只能进行两次交易-123. 买卖股票的最佳时机 III

Leetcode123

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

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

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

示例 1:

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

解题思路:5种状态

dp0 未交易

dp1 买入一次

dp2 卖出一次

dp3 买入两次

dp4 卖出两次

转移方程:

dp0 一直为0

dp1 前一天买入今天未操作/前一天未买入今天 买入

dp2 前一天已卖出/前一天买入 今天卖出

dp3 前一天买入两次的/前一天卖出的今天买入的

dp4 前一天卖出的/前一天买入的今天卖出的

结果:卖出1次或卖出两次中的最大收益

class Solution {
	    public int maxProfit(int[] prices) {
	    	// 数组判断
	    	if(prices==null||prices.length==1||prices.length==0) {
	    		return 0;
	    	}
	    	// 初始化
	    	// 未交易
	    	int dp0 = 0;
	    	// 买入一次
	    	int dp1 = -prices[0];
	    	// 卖出一次
	    	int dp2 = Integer.MIN_VALUE;
	    	// 买入两次
	    	int dp3 = Integer.MIN_VALUE;
	    	// 卖出两次
	    	int dp4 = Integer.MIN_VALUE;
	    	// 遍历
	    	for(int i=1;i<prices.length;i++) {
	    		dp0 = 0;
	    		dp1 = Math.max(dp1,dp0-prices[i]);
	    		dp2 = Math.max(dp2,dp1+prices[i]);
	    		dp3 = Math.max(dp3, dp2-prices[i]);
	    		dp4 = Math.max(dp4, dp3+prices[i]);
	    	}
	    	return Math.max(dp2, dp4);
	    	
	    }
	}

六、多次买卖股票(不能同时参与多笔交易)且最多只能进行K次交易-188. 买卖股票的最佳时机 IV

Leetcode188

给定一个整数数组 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 <= 109
0 <= prices.length <= 1000
0 <= prices[i] <= 1000

初始化:

如果我们把买+卖算一次交易
那么这道题的限制是交易数,天数,手上有没有股票(有股票就不能买,没有股票就不能卖)
建立三维的数组:dp [k] [i] [2]
其中k表示的是交易次数,i表示的是第几天,2表示的是有两种状态:手上没有股票/手上有股票
dp [k] [i] [0] 数组中的数字表示的是到了第i天,交易了k次,手上没有股票获得的最大利益
dp [k] [i] [1] 数组中的数字表示的是到了第i天,交易了k次,手上持有股票获得的最大利益

在我们写转移方程之前我们还要先考虑之前提出来的问题–交易数怎么算,买+卖是一次交易,我的t表示的是交易次数,那我到底是买入的时候t要加一了,还是卖出的时候t才加一?

答:都可以,不同的定义就会有不同的实现

来看一下两者有什么不同
买入就算一次交易:
i天t次交易现在手上不持有 = max(i-1天t次交易手上不持有,i-1天t次交易手上持有 + i天卖出价格prices)
dp[t] [i] [0] = max(dp[t] [i - 1] [0], dp[t] [i - 1] + prices[i]);

i天t次交易现在手上持有 = max(i-1天t次交易手上持有,i-1天t-1次交易手上不持有 - i天买入价格)
dp[t] [i] [1] = max(dp[t] [i - 1] [1], dp[t - 1] [i - 1] [0] - prices[i])

初始化

dp[t] [0] [0] 0天t次交易,手上不持有:可能的 0
dp[t] [0] [1] 0天t次交易,手上持有:不可能(0天没有股票,所以无法买入持有;持有说明至少进行了一次买入,买入就交易,因此这里不可能【不可能意思就是不能从这里转移】
dp[0] [i] [0] i天0次交易,手上不持有:0
dp[0] [i] [1] i天0次交易,手上持有:不可能(不交易手上不可能持有)

class Solution {
	    public int maxProfit(int k, int[] prices) {
	    	if(prices==null||prices.length==0||prices.length==1) {
	    		return 0;
	    	}
	    	// 判断k次交易
	    	if(k>(prices.length/2)) {
	    		int sum = 0;
	    		for(int i=1;i<prices.length;i++) {
	    			int temp = prices[i]-prices[i-1];
	    			sum += (temp>0?temp:0);
	    		}
	    		return sum;
	    	}
	    	
	    	// 动态数组
	    	int[][][] dp = new int[k+1][prices.length+1][2];
	    	// 初始化 交易 0天t次交易 手上持有 不可能
	    	for(int t=0;t<=k;t++) {
	    		dp[t][0][1] = Integer.MIN_VALUE;
	    	}
	    	// i天0次交易手上持有 不可能
	    	for(int i=0;i<prices.length;i++) {
	    		dp[0][i][1] = Integer.MIN_VALUE;
	    	}
	    	// 开始
	    	for(int t=1;t<=k;t++) { // 交易次数
	    		for(int i=1;i<=prices.length;i++) {
	    			dp[t][i][0] = Math.max(dp[t][i-1][0], dp[t][i-1][1]+prices[i-1]);
	    			dp[t][i][1] = Math.max(dp[t][i-1][1], dp[t-1][i-1][0]-prices[i-1]);
	    		}
	    	}
	    	return dp[k][prices.length][0];
	    }
	}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mind_programmonkey

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值