leetcode-买卖股票的最佳时机系列(121,122,123,188)
121 买卖股票的最佳时机 I
给定一只股票,但是只能买卖一次。
暴力法
进行两层循环,第一层循环是买入的日子,第二层循环是卖出的日子,遍历左右的利润,求出最大值。
代码
1 2 3 4 5 6 7 8 9 10 11
| function maxProfit(prices: number[]): number { if (prices.length < 2) return 0; const length = prices.length; let profit = 0; for (let i = 0; i < length; i++) { for (let j = i + 1; j < length; j++) { profit = Math.max(profit, prices[j] - prices[i]); } } return profit; }
|
优化
进行一次遍历,在遍历的过程中修改变量,一个是当前最低的价格,一个是当前的利润。
如果当前价格低于最低价格,则更新最低价格。用当前的价格减去最低价格,即是这一天能够获得的最大利润。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| function maxProfit(prices: number[]): number { if (prices.length < 2) return 0; const length = prices.length; let minPrice = prices[0]; let profit = 0; for (let i = 1; i < prices.length; i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } profit = Math.max(profit, prices[i] - minPrice); } return profit; }
|
122 买卖股票的最佳时机 II
暴力法
对于每个股票交易日,都有两种选择,一种是按兵不动,即不进行任何操作。还有一种是根据当前股票的持有情况,进入买进或者卖出,比如当前持有股票就可以卖出,当前没有股票就进行买入。
每一个交日易都有不同的选择,一次一次的选择形成了一棵操作树,这是典型的 dfs 了。
暴力法代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| function maxProfit(prices: number[]): number { let max = 0; const length = prices.length;
const dfs = (count: number, profit: number, status: number) => { if (count === length) { max = Math.max(max, profit); return; }
dfs(count + 1, profit, status);
if (status === 0) { dfs(count + 1, profit - prices[count], 1); } else if (status === 1) { dfs(count + 1, profit + prices[count], 0); } };
dfs(0, 0, 0);
return max; }
|
贪心算法
这个问题,可以使用贪心算法,即根据当日的价格来决定上个交易日是否进行买入,只要今天的价格比昨天高,就昨天买日,今天卖出。当然现实情况下,是不可能买入昨天的股票的。
贪心法代码
1 2 3 4 5 6 7 8 9 10 11 12
| function maxProfit(prices: number[]): number { if (!prices || prices.length < 2) return 0; let profit = 0;
for (let i = 1; i < prices.length; i++) { if (prices[i] > prices[i - 1]) { profit += prices[i] - prices[i - 1]; } }
return profit; }
|
动态规划
如果使用动态规划来解决这个问题,那么需要保存一个二维的状态,dp[i][j]。
dp[i][0] 代表第 i 天,手上没有股票时的收益。dp[i][1]代表第 i 天手上有股票的收益。
那么推导一下状态方程就是
- dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i])
- 也就是第 i 天手上没有股票时,可以是 i-1 天本来就没有股票,然后第 i 天不进行任何操作,或者是 i-1 天持有股票,然后将其卖出。取两者的最大值。
- dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i])
- 也就是第 i 天手上有股票时,可以是 i-1 天本来就有股票,然后第 i 天按兵不动,或者第 i-1 天手上没有骨片,然后今天买进。取两者的最大值。
最后我们期望的结果就是 dp[n][0]。也就是最后一天手上没有股票时的金额。
可以看出第 n 天的最大收益,是根据之前的每一天的收益情况,一点一点推导出来的。非常经典的动态规划。
动态规划代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function maxProfit(prices: number[]): number { if (!prices || prices.length < 2) return 0;
const dp = prices.map(() => [0, 0]); dp[0][0] = 0; dp[0][1] = -prices[0];
for (let 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][1], dp[i - 1][0] - prices[i]); }
return dp[prices.length - 1][0]; }
|
123 买卖股票的最佳时机 III
不同于买卖股票的最佳时机 II。这次只能交易两次了。这次我们只选择动态规划来解决这个问题。我们把买进股票作为交易一次股票的标志。
动态规划
首先需要增加一个状态,来记录当前已经交易了几次。dp[i][j][k] ,j 代表了是第几次买进股票了。可以的取值为 0,1,2。k 还是代表当前手上是否持有股票,0 表示不持有,1 表示持有。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| function maxProfit(prices: number[]): number { if (prices.length < 2) return 0; const length = prices.length; const dp = prices.map(() => Array(2)); for (let i = 0; i < length; i++) { for (let j = 0; j <= 2; j++) { dp[i][j] = []; } }
for (let i = 0; i < length; i++) { dp[i][0][0] = 0; dp[i][0][1] = -Infinity; } for (let j = 0; j <= 2; j++) { dp[0][j][0] = 0; dp[0][j][1] = -pirces[0]; }
for (let i = 1; i < length; i++) { for (let j = 1; j <= 2; j++) { dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); } }
let maxProfit = 0; for (let j = 0; j <= 2; j++) { maxProfit = Math.max(maxProfit, dp[length - 1][j][0]); } return maxProfit; }
|
188 买卖股票的最佳时机 IV
这一次又升级了,可以买卖 k 次
动态规划
状态方程可以参考买卖两次。其实已经将算法范型化,支持将 2 次变成 k 次就可以了。
需要注意的是,当 k>=len/2 的时候,问题相当于变成了可以交易无数次,也就是 122,这时可以直接采用 122 的贪心算法。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| function greedy(prices: number[]): number { if (!prices || prices.length < 2) return 0; let profit = 0;
for (let i = 1; i < prices.length; i++) { if (prices[i] > prices[i - 1]) { profit += prices[i] - prices[i - 1]; } }
return profit; } function maxProfit(k: number, prices: number[]): number { if (prices.length < 2) return 0;
if (k >= prices.length / 2) return greedy(prices);
const length = prices.length; const dp = prices.map(() => Array(k + 1)); for (let i = 0; i < length; i++) { for (let j = 0; j <= k; j++) { dp[i][j] = []; } }
for (let i = 0; i < length; i++) { dp[i][0][0] = 0; dp[i][0][1] = -Infinity; } for (let j = 0; j <= k; j++) { dp[0][j][0] = 0; dp[0][j][1] = -prices[0]; }
for (let i = 1; i < length; i++) { for (let j = 1; j <= k; j++) { dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); } }
let maxProfit = 0; for (let j = 0; j <= k; j++) { maxProfit = Math.max(maxProfit, dp[length - 1][j][0]); } return maxProfit; }
|