【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费

文章目录

  • 一、121、LeetCode买卖股票的最佳时机
    • 1.1 动态规划
    • 1.2 动态规划-滚动数组
  • 二、122、买卖股票的最佳时机 II
  • 三、123、买卖股票的最佳时机 III
  • 四、188、买卖股票的最佳时机 IV
  • 五、309、买卖股票的最佳时机含冷冻期
  • 六、714、买卖股票的最佳时机含手续费
  • 七、完整代码

所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。

一、121、LeetCode买卖股票的最佳时机

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第1张图片

1.1 动态规划

  思路分析

  • 第一步,动态数组的含义。本题股票买卖只能进行一次,需要考虑再第 i i i天的时候要不要持有股票(注意持有股票未必是买入第 i i i天的股票,也有可能购买前面天数的股票。它是一种状态,不是一种动作),由此产生了持有和不持有的两种状态。因此我们根据天数和是否持有股票这两个维度创建二维动态数组 d p [ i ] [ j ] dp[i][j] dp[i][j] d p [ i ] [ j ] dp[i][j] dp[i][j]一共两列: d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表持有股票所得最多现金, d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表不持有股票所得的最多现金。其中, i i i代表天数。假设最初的现金为 0 0 0,那么加入第 i i i天买入股票现金就是 − p r i c e s [ i ] -prices[i] prices[i](负数)。
  • 第二步,递推公式。 d p [ i ] [ 0 ] dp[i][0] dp[i][0]可以由两种情况得出:第 i − 1 i-1 i1天就持有股票了(只能买一次,之前买过),那么 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0] = dp[i-1][0] dp[i][0]=dp[i1][0];第 i i i天买入股票,那么 d p [ i ] [ 0 ] = − p r i c e s [ i ] dp[i][0] = -prices[i] dp[i][0]=prices[i]。因为要获取最大利润,那么只能挑便宜的买(这两个值都是负数),故我们在二者之间取最大值: d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , − p r i c e s [ i ] ) dp[i][0] = max(dp[i - 1][0], -prices[i]) dp[i][0]=max(dp[i1][0],prices[i])。同理, d p [ i ] [ 1 ] dp[i][1] dp[i][1]也可以由两种情况得出:第 i − 1 i-1 i1天不持有股票(已经卖出或者还未买入),那么保持现状 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] dp[i][1] = dp[i - 1][1] dp[i][1]=dp[i1][1];第 i i i天卖出股票,有 d p [ i ] [ 1 ] = p r i c e s [ i ] + d p [ i − 1 ] [ 0 ] dp[i][1] = prices[i] + dp[i - 1][0] dp[i][1]=prices[i]+dp[i1][0]。因为要挑贵的卖出,故取最大值: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , p r i c e s [ i ] + d p [ i − 1 ] [ 0 ] ) dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]) dp[i][1]=max(dp[i1][1],prices[i]+dp[i1][0])
  • 第三步,元素初始化。令 d p [ 0 ] [ 0 ] = − p r i c e s [ 0 ] dp[0][0] = -prices[0] dp[0][0]=prices[0] d p [ 0 ] [ 1 ] = 0 dp[0][1] = 0 dp[0][1]=0
  • 第四步,递归顺序。循环从 i = 1 i = 1 i=1开始。
  • 第五步,打印结果。最终必然是要卖出股票,状态是不持有股票,所以返回 d p [ p r i c e s . s i z e ( ) − 1 ] [ 1 ] dp[prices.size() - 1][1] dp[prices.size()1][1]

  程序如下

// 121、股票I-动态规划
class Solution {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], -prices[i]);				// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);	// 第i天不持有股票
		}
		return dp[prices.size() - 1][1];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

1.2 动态规划-滚动数组

  在上面程序的基础之上,我们发现对于 d p [ i ] [ j ] dp[i][j] dp[i][j]来说,只利用了 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j],因此空间不必开辟那么大。利用滚动数组,我们将空间复杂度降低到 O ( 1 ) O(1) O(1)
  程序如下

// 121、股票I-动态规划-滚动数组
class Solution2 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(2, vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);				// 第i天持有股票
			dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);	// 第i天不持有股票
		}
		return dp[(prices.size() - 1) % 2][1];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

二、122、买卖股票的最佳时机 II

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第2张图片

  思路分析:本题与121题不同之处在于可以多次买卖,但是买入一只股票之前不能持有其他股票。因为这一点不一样,所以当第 i i i天买入股票的时候,所持有的现金可能有之前买卖过的利润。那么第 i i i天持有股票即 d p [ i ] [ 0 ] dp[i][0] dp[i][0],如果是第 i i i天买入股票,所得现金就是昨天不持有股票的所得现金减去今天的股票价格 即: d p [ i − 1 ] [ 1 ] − p r i c e s [ i ] dp[i - 1][1] - prices[i] dp[i1][1]prices[i]。其余部分的分析和121题完全一样。
  程序如下

// 122、股票II-动态规划
class Solution3 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);	// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);	// 第i天不持有股票
		}
		return dp[prices.size() - 1][1];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

  同理,本题也可以用滚动数组降低空间复杂度。程序如下

// 122、股票II-动态规划-滚动数组
class Solution4 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(2, vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);	// 第i天持有股票
			dp[i % 2][1] = max(dp[(i - 1) % 2][1], dp[(i - 1) % 2][0] + prices[i]);	// 第i天不持有股票
		}
		return dp[(prices.size() - 1) % 2][1];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

三、123、买卖股票的最佳时机 III

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第3张图片

  思路分析:本题又是另外一个变体,最多可有完成两笔交易,同时买入一只股票之前不能持有其他股票。一天一共只有五个状态:1、没有操作;2、第一次持有股票;3、第一次不持有股票;4、第二次持有股票;5、第二次不持有股票。套用121题的思路,我们创建一个二维数组,行代表天数,列代表状态。

  1. dp数组的含义。其中, d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表第 i i i天没有操作; d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表第 i i i天第一次持有股票; d p [ i ] [ 2 ] dp[i][2] dp[i][2]代表第 i i i天第一次不持有股票; d p [ i ] [ 3 ] dp[i][3] dp[i][3]代表第 i i i天第二次持有股票; d p [ i ] [ 4 ] dp[i][4] dp[i][4]代表第 i i i天第二次不持有股票。

  2. 递推公式。
      除了 d p [ i ] [ 0 ] dp[i][0] dp[i][0]以外, d p [ i ] [ j ] dp[i][j] dp[i][j]都可以由两种情况得出。 d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表没有操作,令 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0] = dp[i - 1][0] dp[i][0]=dp[i1][0] d p [ i ] [ 1 ] dp[i][1] dp[i][1]这种情况要么是在第 i i i天买了股票,要么是在之前买了股票:

  • i i i天没有买入股票(之前买的),那么 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 1 ] dp[i][1] = dp[i-1][1] dp[i][1]=dp[i1][1]
  • i i i天买入股票,那么 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] dp[i][1] = dp[i-1][0] - prices[i] dp[i][1]=dp[i1][0]prices[i]

  二者取最大值 d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i])。这种情况要么是延续之前的状态,要么是在第 i i i天卖出:

  • i i i天没有卖股票(之前卖的),那么 d p [ i ] [ 2 ] = d p [ i − 1 ] [ 2 ] dp[i][2] = dp[i-1][2] dp[i][2]=dp[i1][2]
  • i i i天卖了股票,那么 d p [ i ] [ 2 ] = d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] dp[i][2] = dp[i-1][1] + prices[i] dp[i][2]=dp[i1][1]+prices[i]

  二者取最大值 d p [ i ] [ 2 ] = m a x ( d p [ i − 1 ] [ 2 ] , d p [ i − 1 ] [ 1 ] − p r i c e s [ i ] ) dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i]) dp[i][2]=max(dp[i1][2],dp[i1][1]prices[i])。同理可得剩下两个状态的递推公式:

  • d p [ i ] [ 3 ] = m a x ( d p [ i − 1 ] [ 3 ] , d p [ i − 1 ] [ 2 ] − p r i c e s [ i ] ) dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]) dp[i][3]=max(dp[i1][3],dp[i1][2]prices[i])
  • d p [ i ] [ 4 ] = m a x ( d p [ i − 1 ] [ 4 ] , d p [ i − 1 ] [ 3 ] + p r i c e s [ i ] ) dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]) dp[i][4]=max(dp[i1][4],dp[i1][3]+prices[i])
  1. 动态数组初始化。
		dp[0][0] = 0;			// 第一天没有操作
		dp[0][1] -= prices[0];	// 第一天第一次持有股票 = 第一天买入
		dp[0][2] = 0;			// 第一天第一次不持有股票 = 第一天买入,又卖出
		dp[0][3] -= prices[0];	// 第一天第二次持有股票 = 第一天买入,卖出,再买入
		dp[0][4] = 0;			// 第一天第二次不持有股票 = 第一天买入卖出两次
  1. 递归顺序。从1开始到结束即可。
  2. 打印出dp数组验证。

  程序如下

// 123、股票III-动态规划
class Solution {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(5));	// 二维数组,行代表天数,列代表不同状态
		dp[0][0] = 0;			// 第一天没有操作
		dp[0][1] -= prices[0];	// 第一天第一次持有股票 = 第一天买入
		dp[0][2] = 0;			// 第一天第一次不持有股票 = 第一天买入,又卖出
		dp[0][3] -= prices[0];	// 第一天第二次持有股票 = 第一天买入,卖出,再买入
		dp[0][4] = 0;			// 第一天第二次不持有股票 = 第一天买入卖出两次

		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = dp[i-1][0];									// 第i天没有操作
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);	// 第i天第一次持有股票
			dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);	// 第i天第一次不持有股票
			dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);	// 第i天第二次持有股票
			dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);	// 第i天第二次不持有股票
		} 
		return dp[prices.size() - 1][4];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

  优化初始化的代码以及动态数组。程序当中,虽然dp[2]利用的是当天的dp[1],但结果也是对的。dp[1] = max(dp[1], dp[0] - prices[i]); 如果dp[1]取dp[1],即保持买入股票的状态,那么 dp[2] = max(dp[2], dp[1] + prices[i]);中dp[1] + prices[i] 就是今天卖出。如果dp[1]取dp[0] - prices[i],今天买入股票,那么dp[2] = max(dp[2], dp[1] + prices[i]);中的dp[1] + prices[i]相当于是今天再卖出股票,一买一卖收益为0,对所得现金没有影响。相当于今天买入股票又卖出股票,等于没有操作,保持昨天卖出股票的状态了。这种写法虽然简单,但是理解起来很复杂,使用上一个版本的代码足以

// 123、股票III-动态规划-滚动数组
class Solution {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<int> dp(5, 0);	// 行代表天数,列代表不同状态
		dp[1] -= prices[0];		// 第一天第一次持有股票 = 第一天买入
		dp[3] -= prices[0];		// 第一天第二次持有股票 = 第一天买入,卖出,再买入
		for (int i = 1; i < prices.size(); i++) {
			dp[1] = max(dp[1], dp[0] - prices[i]);	// 第i天第一次持有股票
			dp[2] = max(dp[2], dp[1] + prices[i]);	// 第i天第一次不持有股票
			dp[3] = max(dp[3], dp[2] - prices[i]);	// 第i天第二次持有股票
			dp[4] = max(dp[4], dp[3] + prices[i]);	// 第i天第二次不持有股票
		}
		return dp[4];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

四、188、买卖股票的最佳时机 IV

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第4张图片

  思路分析:本题是123、买卖股票的最佳时机 III的变体,买卖次数从两次变成了 k k k次。有了123题作为基础,我们很容易想到,买卖 k k k次其实就有了 2 k + 1 2k+1 2k+1中情况。我们创建一个行为 n n n,列为 2 k + 1 2k+1 2k+1的二维数组。我们根据显而易见的规律写出如下代码:

// 188、股票IV-动态规划
class Solution7 {
public:
	int maxProfit(int k, vector<int>& prices) {
		vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));	// 二维数组,行代表天数,列代表不同状态
		for (int i = 1; i < 2 * k; i += 2) {
			dp[0][i] -= prices[0];	// 第一天第i次持有股票
		}
		for (int i = 1; i < prices.size(); i++) {
			for (int j = 0; j < 2 * k - 1; j +=2) {
				dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);		// 第i天第j次持有股票
				dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);	// 第i天第j次不持有股票
			}
		}
		return dp[prices.size() - 1][2 * k];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ∗ k ) O(n*k) O(nk)
  • 空间复杂度: O ( n ∗ k ) O(n*k) O(nk)

五、309、买卖股票的最佳时机含冷冻期

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第5张图片

  思路分析:本题是122题的变体,允许多次买卖,每次卖出之后存在冷冻期。因为存在冷冻期,我们将不持有股票情况分成两类:延续之前不持有股票的状态和今天卖出股票。因此这道题的情况有四种:1、持有股票;2、延续之前不持有股票的状态;3、今天卖出股票;4、冷冻期状态。

  1. 动态数组的含义。我们创建一个行为 n n n,列为4的二维数组。其中, d p [ i ] [ 0 ] dp[i][0] dp[i][0]代表第 i i i天持有股票的最大收益; d p [ i ] [ 1 ] dp[i][1] dp[i][1]代表第 i i i天延续之前不持有股票的状态的最大收益; d p [ i ] [ 2 ] dp[i][2] dp[i][2]代表第 i i i天卖出股票的最大收益; d p [ i ] [ 3 ] dp[i][3] dp[i][3]代表第 i i i天为冷冻期的最大收益。
  2. 递推公式。
    情况一 d p [ i ] [ 0 ] dp[i][0] dp[i][0]可以由两种情况得出:
  • i − 1 i-1 i1天就持有股票了(只能买一次,之前买过),那么 d p [ i ] [ 0 ] = d p [ i − 1 ] [ 0 ] dp[i][0] = dp[i-1][0] dp[i][0]=dp[i1][0]
  • i i i天买入股票,那么 d p [ i ] [ 0 ] = 之前最大收益 − p r i c e s [ i ] dp[i][0] = 之前最大收益-prices[i] dp[i][0]=之前最大收益prices[i]。之前最大收益来自 d p [ i − 1 ] [ 1 ] dp[i-1][1] dp[i1][1] d p [ i − 1 ] [ 3 ] dp[i-1][3] dp[i1][3](因为冷冻期的存在,不能来自 d p [ i − 1 ] [ 2 ] dp[i-1][2] dp[i1][2]),故我们有 之前最大收益 = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 3 ] ) 之前最大收益 = max(dp[i-1][1], dp[i-1][3]) 之前最大收益=max(dp[i1][1],dp[i1][3])

  因为要获取最大利润,故我们在二者之间取最大值: d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 3 ] ) − p r i c e s [ i ] ) dp[i][0] = max(dp[i - 1][0], max(dp[i-1][1], dp[i-1][3])-prices[i]) dp[i][0]=max(dp[i1][0],max(dp[i1][1],dp[i1][3])prices[i])

  情况二 d p [ i ] [ 1 ] dp[i][1] dp[i][1]可以由状态二和冷冻期得出: d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 3 ] ) dp[i][1] = max(dp[i-1][1], dp[i-1][3]) dp[i][1]=max(dp[i1][1],dp[i1][3])
  情况三,要想达到今天卖出股票的状态,那么昨天必然持有股票。 d p [ i ] [ 2 ] dp[i][2] dp[i][2]只能由 d p [ i − 1 ] [ 0 ] dp[i-1][0] dp[i1][0]得出: d p [ i ] [ 2 ] = d p [ i − 1 ] [ 0 ] + p r i c e s [ i ] dp[i][2] = dp[i-1][0] + prices[i] dp[i][2]=dp[i1][0]+prices[i]
  情况四,要想达到冷冻期,那么昨天必然卖出了股票。因此 d p [ i ] [ 3 ] dp[i][3] dp[i][3]只能由 d p [ i − 1 ] [ 2 ] dp[i-1][2] dp[i1][2]得出: d p [ i ] [ 3 ] = d p [ i − 1 ] [ 2 ] dp[i][3] = dp[i-1][2] dp[i][3]=dp[i1][2]

	dp[i][0] = max(dp[i - 1][0], max(dp[i-1][1], dp[i-1][3])-prices[i]);	// 状态1
	dp[i][1] = max(dp[i-1][1], dp[i-1][3]);									// 状态2
	dp[i][2] = dp[i-1][0] + prices[i];										// 状态3
	dp[i][3] = dp[i-1][2];													// 状态4
  1. 元素初始化。令 d p [ 0 ] [ 0 ] = − p r i c e s [ 0 ] , d p [ 0 ] [ 1 ] = 0 , d p [ 0 ] [ 2 ] = 0 , d p [ 0 ] [ 3 ] = 0 dp[0][0] = -prices[0], dp[0][1] = 0, dp[0][2] = 0, dp[0][3] = 0 dp[0][0]=prices[0]dp[0][1]=0dp[0][2]=0dp[0][3]=0
  2. 递归顺序。循环从 i = 1 i = 1 i=1开始,从前往后遍历。
  3. 打印结果。最终必然是要卖出股票,状态是不持有股票,所以返回状态二、三和四中的最大值:
	return max(dp[prices.size() - 1][1], max(dp[prices.size() - 1][2], dp[prices.size() - 1][3]));

  程序如下

class Solution8 {
public:
	int maxProfit(vector<int>& prices) {
		vector<vector<int>> dp(prices.size(), vector<int>(4, 0));	// 二维数组,行代表天数,列代表不同状态
		dp[0][0] = -prices[0];
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][3]) - prices[i]);	// 状态1
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);									// 状态2
			dp[i][2] = dp[i - 1][0] + prices[i];										// 状态3
			dp[i][3] = dp[i - 1][2];													// 状态4		
		}
		return max(dp[prices.size() - 1][1], max(dp[prices.size() - 1][2], dp[prices.size() - 1][3]));
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

六、714、买卖股票的最佳时机含手续费

【算法与数据结构】121、122、123、188、309、714、LeetCode买卖股票的最佳时机I II III IV+含冷冻期+含手续费_第6张图片

  思路分析:本题是122题的变体,在122题的基础上加上了手续费。一次完整的交易(买入一次+卖出一次)需要支付一次手续费。我们可以假设手续费在卖出股票的时候才会扣除。

  程序如下

// 714、股票+手续费-动态规划
class Solution9 {
public:
	int maxProfit(vector<int>& prices, int fee) {
		vector<vector<int>> dp(prices.size(), vector<int>(2, 0));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);			// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);	// 第i天不持有股票
		}
		return  dp[prices.size() - 1][1];
	}
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)

七、完整代码

# include 
# include 
using namespace std;

// 121、股票I-动态规划
class Solution {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], -prices[i]);				// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);	// 第i天不持有股票
		}
		return dp[prices.size() - 1][1];
	}
};

// 121、股票I-动态规划-滚动数组
class Solution2 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(2, vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);				// 第i天持有股票
			dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);	// 第i天不持有股票
		}
		return dp[(prices.size() - 1) % 2][1];
	}
};

// 122、股票II-动态规划
class Solution3 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);	// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);	// 第i天不持有股票
		}
		return dp[prices.size() - 1][1];
	}
};

// 122、股票II-动态规划-滚动数组
class Solution4 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(2, vector<int>(2));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		dp[0][1] = 0;
		for (int i = 1; i < prices.size(); i++) {
			dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);	// 第i天持有股票
			dp[i % 2][1] = max(dp[(i - 1) % 2][1], dp[(i - 1) % 2][0] + prices[i]);	// 第i天不持有股票
		}
		return dp[(prices.size() - 1) % 2][1];
	}
};

// 123、股票III-动态规划
class Solution5 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<vector<int>> dp(prices.size(), vector<int>(5));	// 二维数组,行代表天数,列代表不同状态
		dp[0][0] = 0;			// 第一天没有操作
		dp[0][1] -= prices[0];	// 第一天第一次持有股票 = 第一天买入
		dp[0][2] = 0;			// 第一天第一次不持有股票 = 第一天买入,又卖出
		dp[0][3] -= prices[0];	// 第一天第二次持有股票 = 第一天买入,卖出,再买入
		dp[0][4] = 0;			// 第一天第二次不持有股票 = 第一天买入卖出两次

		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = dp[i-1][0];									// 第i天没有操作
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);	// 第i天第一次持有股票
			dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);	// 第i天第一次不持有股票
			dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);	// 第i天第二次持有股票
			dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);	// 第i天第二次不持有股票
		} 
		return dp[prices.size() - 1][4];
	}
};

// 123、股票III-动态规划-滚动数组
class Solution6 {
public:
	int maxProfit(vector<int>& prices) {
		if (prices.size() == 0) return 0;
		vector<int> dp(5, 0);	// 行代表天数,列代表不同状态
		dp[1] -= prices[0];		// 第一天第一次持有股票 = 第一天买入
		dp[3] -= prices[0];		// 第一天第二次持有股票 = 第一天买入,卖出,再买入
		for (int i = 1; i < prices.size(); i++) {
			dp[1] = max(dp[1], dp[0] - prices[i]);	// 第i天第一次持有股票
			dp[2] = max(dp[2], dp[1] + prices[i]);	// 第i天第一次不持有股票
			dp[3] = max(dp[3], dp[2] - prices[i]);	// 第i天第二次持有股票
			dp[4] = max(dp[4], dp[3] + prices[i]);	// 第i天第二次不持有股票
		}
		return dp[4];
	}
};

// 188、股票IV-动态规划
class Solution7 {
public:
	int maxProfit(int k, vector<int>& prices) {
		vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));	// 二维数组,行代表天数,列代表不同状态
		for (int i = 1; i < 2 * k; i += 2) {
			dp[0][i] -= prices[0];	// 第一天第i次持有股票
		}
		for (int i = 1; i < prices.size(); i++) {
			for (int j = 0; j < 2 * k - 1; j +=2) {
				dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);		// 第i天第j次持有股票
				dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);	// 第i天第j次不持有股票
			}
		}
		return dp[prices.size() - 1][2 * k];
	}
};

// 309、股票+冷冻期-动态规划
class Solution8 {
public:
	int maxProfit(vector<int>& prices) {
		vector<vector<int>> dp(prices.size(), vector<int>(4, 0));	// 二维数组,行代表天数,列代表不同状态
		dp[0][0] = -prices[0];
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][1], dp[i - 1][3]) - prices[i]);	// 状态1
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);									// 状态2
			dp[i][2] = dp[i - 1][0] + prices[i];										// 状态3
			dp[i][3] = dp[i - 1][2];													// 状态4		
		}
		return max(dp[prices.size() - 1][1], max(dp[prices.size() - 1][2], dp[prices.size() - 1][3]));
	}
};

// 714、股票+手续费-动态规划
class Solution9 {
public:
	int maxProfit(vector<int>& prices, int fee) {
		vector<vector<int>> dp(prices.size(), vector<int>(2, 0));	// 二维数组,行代表天数,列代表是否持有股票
		dp[0][0] -= prices[0];
		for (int i = 1; i < prices.size(); i++) {
			dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);			// 第i天持有股票
			dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);	// 第i天不持有股票
		}
		return  dp[prices.size() - 1][1];
	}
};

int main() {
	int k = 2;
	int fee = 2;
	vector<int> prices = { 7,1,5,3,6,4 };
	//vector prices = { 3, 2, 6, 5, 0, 3 };	
	Solution9 s1;
	int result = s1.maxProfit(prices, fee);
	//int result = s1.maxProfit(k, prices);
	//int result = s1.maxProfit(prices);
	cout << result << endl;
	system("pause");
	return 0;
}

end

你可能感兴趣的:(算法,算法)