LeetCode 股票系列问题

买卖股票系列问题概述

基本上都是给定一个数组表明接下来的n天内的股票价格,在已知股票价格的你可以进行买卖操作从而获得足够多的利润。
思路:
无论是否规定买卖操作的次数,我们都可以穷举出每天可能的状态,在每天的状态下我们有三种选择(即买进、卖出、无操作),但并不是每天都可以进行买进或卖出,当手里持有股票的前提下才可以进行卖出,在手里没有股票(当规定操作次数时,K>0时)才可以买进,无操作每天都可以。
穷举:
我们可以通过一个三维数组来穷举所有状态;
第一个是天数,第二个是剩余可操作次数(买进卖出为一次操作),第三个是此时手里有无股票(0代表没有,1代表持有)

int n = price.length
for(int i = 0; i < n; i++){
	for( int k = maxk; k > 0; k--){
		for{ 0 or 1}
	}
}

共有i * k * 2个状态;
state[3] [4] [1]表达的意义就是:今天是第三天,还可以交易操作4次,此时手里持有股票;
state[5] [1] [0]表达的意义就是:今天是第五天,还可以交易操作1次,此时手里没有股票;

利润:
我们最终所要求的就是最后一天时所获得的最大利润;
那么,最后一天的状态即为:
state[n-1] [k] [0] 和 state [n-1] [k] [0],很明显最大利润是state[n-1] [k] [0] ,因为 [0]表示最后一天手里的股票卖出了,获得的利润必然会比手里还有股票所获得的利润大。

状态转移方程:
穷举完所有状态之后来看看这些状态是怎么得到的(即选择),当然我们初衷是利润最大化;

state [i][k][0] = Math.math ( state[i-1][k][0] , state[i-1][k][1] + price[i]);
				  Math.math (    选择无操作,             选择卖出              );

解释:今天手里没有股票有两种可能;
第一种:前一天手里也没有股票,并且今天选择不操作;
第二种:前一天手里持有股票,今天选择卖出,因为是今天卖的,所以应该利润加上今天股票的价格。

state [i][k][1] = Math.math ( state[i-1][k][1] , state[i-1][k-1][0] - price[i]);
				  Math.math (    选择无操作,             选择买进            );

解释:今天手里持有股票有两种可能;
第一种:前一天手里持有股票,并且今天选择不操作;
第二种:前一天手里没有有股票,今天选择买进,因为是今天买的,所以应该利润减去今天股票的价格。

此时所有股票系列的题都可以套这个框架解决了,但是还应该处理下特殊情况:

state[-1] [k] [0] = 0;

解释:i从0开始,当i= -1时,市场还没开,利润当然为0;

state[-1] [k] [1] = -infinity;

解释:i从0开始,当i= -1时,不可能持有股票,负无穷表示这种状态是不可能的;

state[i] [0] [0] = 0;

解释:当不允许买卖操作时,利润肯定为0;

state[i] [0] [1] = -infinity;

解释:当不允许操作时,是不可能持有股票的,负无穷表示这种状态是不可能的;

开始做题:

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

思路:
k = 1
直接套状态方程,因为k=1,对状态方程没有影响,所以可以去掉k,根据特殊情况简化方程为:
状态方程

state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i]);
state[i][1] = Math.max(state[i-1][1], -prices[i]);

代码:

int n = prices.length;
int[][] state = new int[n][2];
for (int i = 0; i < n; i++) {
    state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i]);
    state[i][1] = Math.max(state[i-1][1], -prices[i]);
}
return state[n - 1][0];

当i = 0时,state[i-1]是不合法的,且新状态只和相邻的一个状态有关,其实不用整个 state数组,只需要一个变量储存相邻的那个状态就足够了,这样可以把空间复杂度降到 O(1)所以处理之后;
代码:

	public int maxProfit_k_1(int[] prices) {
		int n = prices.length;
		int state_i_0= 0, state_i_1 = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			// state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i])
			state_i_0 = Math.max(state_i_0, state_i_1 + prices[i]);
			// state[i][1] = Math.max(state[i-1][1], -prices[i])
			state_i_1 = Math.max(state_i_1, -prices[i]);
		}
		return state_i_0;
	}

LeetCode122:k= + infinity*

思路1:
如果 k 为正无穷,那么就可以认为 k 和 k - 1 是一样的(因为数组中的k的变化并不影响结果),可以这样改写框架:
状态方程

state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i]);
state[i][1] = Math.max(state[i-1][1], state[i-1][0] - prices[i]);

代码1:

	public int maxProfit_k_inf(int[] prices) {
		int n = prices.length;
		int state_i_0 = 0, state_i_1 = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			int temp = state_i_0;
			state_i_0 = Math.max(state_i_0, state_i_1 + prices[i]);
			state_i_1 = Math.max(state_i_1, temp - prices[i]);
		}
		return state_i_0;
	}

思路2:
只要将第二天比第一天高的利润加起来就可以,和和状态方程无关

代码2:

 	public int maxProfit2(int[] prices) {
	        int maxprofit = 0;
	        for (int i = 1; i < prices.length; i++) {
	            if (prices[i] > prices[i - 1])
	                maxprofit += prices[i] - prices[i - 1];
	        }
	        return maxprofit;
	    }

LeetCode123:k = 2

思路:
当k=2时,k的变化就和利润有关了,不能无视了,所以用原始的框架直接带进去,当然要处理特殊情况;
状态方程

state[i][k][0] = Math.max(state[i-1][k][0], state[i-1][k][1] + prices[i]);
state[i][k][1] = Math.max(state[i-1][k][1], state[i-1][k-1][0] - prices[i]);

代码:

	public int maxProfit(int[] prices) {
		int n = prices.length;
		int max_k = 2;
		int[][][] state = new int[n][max_k + 1][2];
		for (int i = 0; i < n; i++) {
			for (int k = max_k; k >= 1; k--) {
				if (i - 1 == -1) {
					state[i][k][0] = 0;
					//第一天不买利润就为0
					state[i][k][1] = -prices[i];
					//第一天就买,利润就减去第一天的价格
					continue;
				}

				state[i][k][0] = Math.max(state[i - 1][k][0], state[i - 1][k][1] + prices[i]);
				state[i][k][1] = Math.max(state[i - 1][k][1], state[i - 1][k - 1][0] - prices[i]);
			}
		}
		return state[n - 1][max_k][0];
	}

改进:
这里 k 取值范围比较小,所以可以不用 for 循环,直接把 k = 1 和 2 的情况手动列举出来也可以;
代码:

	public int maxProfit_k_2(int[] prices) {
		int state_i10 = 0, state_i11 = Integer.MIN_VALUE;
		int state_i20 = 0, state_i21 = Integer.MIN_VALUE;
		for (int price : prices) {
			state_i20 = Math.max(state_i20, state_i21 + price);
			state_i21 = Math.max(state_i21, state_i10 - price);
			state_i10 = Math.max(state_i10, state_i11 + price);
			state_i11 = Math.max(state_i11, -price);
		}
		return state_i20;
	}

LeetCode.309:卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)
思路:
隔一天操作其实和每天都可以操作一样,只需要买的时候应该从前两天(i-2)的基础上买;
状态方程

state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i])
state[i][1] = Math.max(state[i-1][1], state[i-2][0] - prices[i])
解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 

代码:

	public int maxProfit_with_cool(int[] prices) {
		int n = prices.length;
		int state_i_0 = 0, state_i_1 = Integer.MIN_VALUE;
		int state_pre_0 = 0; // 代表 state[i-2][0]
		for (int i = 0; i < n; i++) {
			int temp = state_i_0;
			state_i_0 = Math.max(state_i_0, state_i_1 + prices[i]);
			state_i_1 = Math.max(state_i_1, state_pre_0 - prices[i]);
			state_pre_0 = temp;
		}
		return state_i_0;
	}

LeetCode714:不限操作次数,每次卖出含手续费
思路:
只需在卖出的利润上减去手续费或者在买进的时候加上手续费;
状态方程

state[i][0] = Math.max(state[i-1][0], state[i-1][1] + prices[i])
state[i][1] = Math.max(state[i-1][1], state[i-1][0] - prices[i] - fee)
解释:买进时价格涨高了

代码:

	public int maxProfit_k_inf(int[] prices,int fee) {
		int n = prices.length;
		int state_i_0 = 0, state_i_1 = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			int temp = state_i_0;
			state_i_0 = Math.max(state_i_0, state_i_1 + prices[i]);
			state_i_1 = Math.max(state_i_1, temp - prices[i] -fee);
		}
		return state_i_0;
	}

LeetCode188:k = 任何正整数
思路:
有了上一题 k = 2 的铺垫,这题应该和上一题的第一个解法没啥区别。但是出现了一个超内存的错误,原来是传入的 k 值会非常大,dp 数组太大了。现在想想,交易次数 k 最多有多大呢?
一次交易由买入和卖出构成,至少需要两天。所以说有效的限制 k 应该不超过 n/2,如果超过,就没有约束作用了,相当于 k = +infinity,此时直接调用之前写过的方法即可。

代码:

int maxProfit_k_any(int max_k, int[] prices) {
		int n = prices.length;
		if (max_k > n / 2) {
			//如果大于n/2就没有约束k的必要了,直接调用无限次操作方法
			return maxProfit_k_inf(prices);
		}
		int[][][] state = new int[n][max_k + 1][2];
		for (int i = 0; i < n; i++)
			for (int k = max_k; k >= 1; k--) {
				if (i - 1 == -1) {
					state[i][k][0] = 0;
					state[i][k][1] = -prices[i];
					continue;
				}
				state[i][k][0] = Math.max(state[i - 1][k][0], state[i - 1][k][1] + prices[i]);
				state[i][k][1] = Math.max(state[i - 1][k][1], state[i - 1][k - 1][0] - prices[i]);
			}
		return state[n - 1][max_k][0];
	}

	private int maxProfit_k_inf(int[] prices) {
		int n = prices.length;
		int state_i_0 = 0, state_i_1 = Integer.MIN_VALUE;
		for (int i = 0; i < n; i++) {
			int temp = state_i_0;
			state_i_0 = Math.max(state_i_0, state_i_1 + prices[i]);
			state_i_1 = Math.max(state_i_1, temp - prices[i]);
		}
		return state_i_0;
	}

本文为看过LeetCode上大神题解《一个通用方法团灭 6 道股票问题》后,自己写的复习笔记;
原文链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-w-5/

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值