长大后想做什么?做回小孩!

0%

LeetCode——买卖股票的最佳时机

NO.121 买卖股票的最佳时机 简单 、NO.122 买卖股票的最佳时机II 简单 、NO.123 买卖股票的最佳时机III 困难 、NO.188 买卖股票的最佳时机IV 困难

NO.121 买卖股票的最佳时机 简单

3zzoW9.png

思路一:暴力法 没什么好说的,双重循环计算所有元素两两组合相减的结果,取最大。

1
2
3
4
5
6
7
8
9
public int maxProfit(int[] prices) {
int maxProfit=0;
for (int i = 0; i < prices.length - 1; i++) {
for (int j = i+1; j < prices.length; j++) {
maxProfit=Math.max(maxProfit,prices[j]-prices[i]);
}
}
return maxProfit;
}

时间复杂度:O(n^2)

思路二:优化暴力法到一次遍历 买卖股票从第二天开始我们每天都会”后悔”:后悔没有在之前的最低点进行买入,只有这样我们的收益才会最大化。

由此可见,我们想要当天利益最大化,只需要在过去的某个最低点买入股票就好。所以我们只需要记录曾经出现过的最低点就好。

1
2
3
4
5
6
7
8
9
10
public int maxProfit(int[] prices) {
int minPoint=Integer.MAX_VALUE,maxProfit=0;
for (int i = 0; i < prices.length; i++) {
//记录曾出现过最低点
minPoint=Math.min(prices[i],minPoint);
//当日-曾经的最低
maxProfit=Math.max(maxProfit,prices[i]-minPoint);
}
return maxProfit;
}

时间复杂度:O(n)

从NO.121题不难看出:买股票的最佳时机是曾经!股市有风险,入股需谨慎!(狗头)

单纯的解答本题是比较简单的,但是买卖股票可以算作是一个系列的经典问题,在leetcode上就有本题一系列的变种问题:买卖股票的最佳时机、买卖股票的最佳时机II、买卖股票的最佳时机III、买卖股票的最佳时机IV、买卖股票的最佳时机含冷冻期、买卖股票的最佳时机含手续费。

虽然这些题有难有易,但是既然是一类问题,就有这一些通用的方法。

这六个问题都是由第四个问题简化变种而来的,第四题相较于本题多了一个参数k,限制只能进行k次交易;第一题也就是本题是只进行一次交易,相当于 k = 1;第二题是不限交易次数,相当于 k = +infinity(正无穷);第三题是只进行 2 次交易,相当于 k = 2;剩下两道也是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件,其实就是第二题的变种。

NO.122 买卖股票的最佳时机II 简单

JBeCGT.png

JBe9iV.png

思路一:贪心算法 姊妹题NO.121,区别在于NO.121题只允许交易一次(一次买入、一次卖出),而本题没有交易次数的限制。

所以想到贪心算法,因为我们是”上帝视角”知道”未来股价”,所以只要赚钱我们每天都进行交易。

所谓赚钱就是:”明天i+1股价”-“今天i股价”>0,这种情况今天就买入;否则就是赔钱或者不赚钱的情况,不进行交易。

1
2
3
4
5
6
7
8
9
10
11
12
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int Profit = 0;
for (int i = 0; i < prices.length - 1; i++) {
//今天i买,明天i+1赚钱。就今天买入,明天卖。
int sub = prices[i + 1] - prices[i];
if (sub > 0) {
Profit += sub;
}
}
return Profit;
}

时间复杂度:O(n)

思路二:动态规划 贪心算法已经很好地解决了这个问题,但是我们还是要带入一下DP解法,为后序的姊妹题NO.122买卖股票的最佳时机III、以及等等很多其他的XXX买卖股票问题都是基于本题进行变化的,无非是在本题的基础上增加更多的限制条件。

dp[i][j]的含义:i表示第i天,j表示持股状态(0表示不持股,1表示持股)。dp[i][j]整体就表示第i天持股/不持股时手里的钱。

初始化:dp[0][0]=0,刚开始不赔不赚;dp[0][1]= -prices[0],只支出了。

状态转移:dp[i][0]=Max(dp[i-1][0],dp[i-1][1]+prices[i]),即第i天不持股时手里钱最多为MAX(i-1天不持股i天继续不持股,i-1天持股+i天以当日价格卖掉股票);

同理dp[i][1]=Max(dp[i-1][0]-prices[i],dp[i-1][1]),即第i天持股时手里钱最多为MAX(i-1天不持股-i天以当日价格买入股票,i-1天持股i天继续持股)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
//初始化
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] -= prices[0];
//状态转移
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
}
//最后一天一定是不持股的
return dp[prices.length - 1][0];
}

时间复杂度:O(n)

NO.123 买卖股票的最佳时机III 困难

JsFsEt.png

思路一:动态规划 NO.121是只能买卖一次股票,NO.122是任意买卖股票,而本题是只能买卖股票两次。

相较于前两题的状态,本题又增加了一个交易次数的状态k。

dp[i][j][k]的含义:第i天的利润,j表示持股状态(0不持股,1持股),k交易次数(卖出次数0~2)。所以每天有六种状态,思路就是从每天的六种状态中找到利润最大的。

初始化dp[0][0][0]=dp[0][0][1]=dp[0][0][2]=0,啥也没干;dp[0][1][0]=dp[0][1][1]=dp[0][1][2]=-prices[0],第一天持股只有支出。

状态转移dp[i][0][0]=dp[i-1][0][0],第i天不持股交易次数0,第i-1天一定未持股未交易,维持现状;

dp[i][0][1]=MAX(dp[i-1][0][1],dp[i-1][1][0]+prices[i]),第i天不持股1次交易,维持第i-1天的状态,或者第i-1天持股未交易第i天卖出。

dp[i][0][2]=MAX(dp[i-1][0][2],dp[i-1][1][1]+prices[i]),第i天不持股2次交易,维持第i-1天的状态,或者第i-1天持股1次交易第i天卖出。

dp[i][1][0]=MAX(dp[i-1][1][0],dp[i-1][0][0]-prices[i]),第i天持股未交易,维持第i-1天的状态,或者第i-1天不持股未交易第i天买入。

dp[i][1][1]=MAX(dp[i-1][1][1],dp[i-1][0][1]-prices[i]),第i天持股1次交易,维持第i-1天的状态,或者第i-1天不持股1次交易第i天买入。

dp[i][1][2]=dp[i][1][2],第i天持股2次交易,已经到达交易限制,但是手中还持股,不需要考虑一定不能利润最大化。

最终的结果一定是在不持股的三种状态中进行比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
//初始化
int[][][] dp = new int[prices.length][2][3];
dp[0][0][0] = dp[0][0][1] = dp[0][0][2] = 0;
dp[0][1][0] = dp[0][1][1] = dp[0][1][2] = -prices[0];
//状态转移
for (int i = 1; i < prices.length; i++) {
//六种状态
dp[i][0][0] = dp[i - 1][0][0];
dp[i][0][1] = Math.max(dp[i - 1][0][1], dp[i - 1][1][0] + prices[i]);
dp[i][0][2] = Math.max(dp[i - 1][0][2], dp[i - 1][1][1] + prices[i]);
dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][0][0] - prices[i]);
dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][0][1] - prices[i]);
dp[i][1][2] = dp[i - 1][1][2];
}
//最大利润一定是不持股的三种状态之一。
return Math.max(dp[prices.length - 1][0][2],
Math.max(dp[prices.length - 1][0][1], dp[prices.length - 1][0][0]));
}

时间复杂度:O(n) 空间O(n)虽然是三维数组,但2和3是常数。

NO.188 买卖股票的最佳时机IV 困难

YbtGLR.png

思路一:动态规划 在上一题的基础上两次交易,变味了 k 次交易。但是需要考虑的状态,仍然是三个,第 i 天、j 持股状态、k 进行过的交易次数。

虽然 k 从上一题的常量 2 变为了本题的变量,但是 dp 数组的含义不变。但是本题如果使用三维数组会因为 k 值过大,导致内存超出。

所以采用两个一维数组 dpi0[k+1] 和 dpi1[k+1] 表示第 i 天持股(1)或不持股(0)时,不同交易次数下的利润。(上一题也可以采用这个方法,进行空间优化)

实现上,由于本题 k 不是上一题中的常量 2 ,无法手动写死每种情况,所以采用一个 for 循环即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public int maxProfit(int k, int[] prices) {
if (k<=0||prices.length<1)return 0;
//最多只能交易 prices.lenght/2 次
if (k>prices.length/2) k = prices.length/2;
//初始化
int[] dpi0 = new int[k+1];
int[] dpi1 = new int[k+1];
for (int j = 1; j < k + 1; j++) {
dpi1[j] = -prices[0];
}
//状态转移
for (int i = 1; i < prices.length; i++) {
for (int j = k; j >= 1; j--) {
//第i天不持股 = Max(第i-1天不持股,第i-1天持股+第i天出售)
dpi0[j] = Math.max(dpi0[j],dpi1[j]+prices[i]);
//第i天持股 = Max(第i-1天持股,第i-1天不持股-第i天买入)
dpi1[j] = Math.max(dpi1[j],dpi0[j-1]-prices[i]);
}
}
return dpi0[k];
}

时间复杂度:O(n*k) n 共n天

空间复杂度:O(k)


本人菜鸟,有错误请告知,感激不尽!

更多题解和学习记录博客:博客github