Leetcode121,股票只能买卖一次,问最大利润。
示例 :
输入: [7,1,5,3,6,4] 输出: 5 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
思路分析:
法1:双指针:定义双指针,一个指向买入,一个指向卖出,遍历数组,更新最大值。
法2:贪心:因为只有一次买入卖出,保证以最低的价钱买入,然后遍历数组,更新最大值。
法3:动态规划:一天只有两个状态:持有和不持有,遍历数组返回最后一天的情况
dp[i][0]和dp[i][1]
分别代表第i天持有和不持有股票所得的现金。- 状态转移方程:对于当前填持有和不持有,我们分别分析其上一天的情况
- 持有状态,上一天持有或不持有(
现在买入,即-prices[i]
)最大值; - 不持有状态,上一天可以是持有(
现在卖了,即prices[i - 1][0] + prices[i]
)或不持有最大值。
代码实现:代码1:双指针
public int maxProfit(int[] prices) { if (prices == null || prices.length == 0) return 0; int start = 0, end = 1, max = 0; while (start < end && end < prices.length) { if (prices[start] >= prices[end]) { start = end++; } else { max = Math.max(max, prices[end] - prices[start]); end++; } } return max; }
代码2:贪心
public int maxProfit(int[] prices) { if (prices == null || prices.length == 0) return 0; int low = Integer.MAX_VALUE; int max = 0; for (int i = 0; i < prices.length; ++i) { low = Math.min(low, prices[i]); max = Math.max(max, prices[i] - low); } return max; }
代码3:动态规划
public int maxProfit(int[] prices) { int len = prices.length; if (prices == null || len == 0) return 0; int[][] dp = new int[len][2]; dp[0][0] = -prices[0]; dp[0][1] = 0; for (int i = 1; i < len; ++i) { dp[i][0] = Math.max(dp[i - 1][0], - prices[i]); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]); } return dp[len - 1][1]; }
Leetcode122,可以多次买卖股票,问最大收益。
示例:
输入: [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
思路分析:
法1:贪心算法:实现上述需求每次交易只要买入小于卖出,即保证每次交易都有收益即可!
法2:动态规划:这里与上题唯一的区别是多次买卖股票。关键点:
- 当
dp[i][0]
,即第i天持有股票时,是在之前利润(前一天卖出的最大利润,前一天必须卖出)基础上在买入,即dp[i - 1][1] - prices[i]
。,上题只有一次买入。 - 对于第i天不持有股票,与上边相同。
代码实现:代码1:贪心
public int maxProfit(int[] prices) { int n = prices.length; if (prices == null || n == 0) return 0; int max = 0; for (int i = 1; i < n; i++) { if (prices[i] > prices[i - 1]) { max += prices[i] - prices[i - 1]; } } return max; }
代码2:动态规划
public int maxProfit(int[] prices) { int len = prices.length; if (prices == null || len == 0) return 0; int[][] dp = new int[len][2]; dp[0][0] = -prices[0]; dp[0][1] = 0; for (int i = 1; i < len; ++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[len - 1][1]; }
Leetcode123,最多买卖两次,问最大收益。
示例 :
输入: [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 5 2 8 3 10
第一天买第二天卖,第三天买第四天卖,第五天买第六天卖,三次收益分别是 4
,6
,7
,最高的两次就是 6 + 7 = 13
了,但是我们第二天其实可以不卖出,第四天再卖出,那么收益是 8 - 1 = 7
,再加上第五天买入第六天卖出的收益就是 7 + 7 = 14
了。
所以当达到了一个高点时不一定要卖出!!!---用动态规划(动态规划关键就是数组定义和状态转移方程!!!)
法1:动态规划:当然我们可以暴力的想一下,当我们计算第 i
天的收益时进行了k
次交易,我们可以选择:
- 不买入卖出,即第
i
天的收益 等于第i - 1
天的收益. - 卖出,追求更大收益,这里就要在
0
到i-1
天就要选择一天买入。多选择了一次买入,那在买入之前已经进行了k-1
次买卖。
即:当用 dp[i][k]
表示前i
天最多交易k
次的最高收益(数组定义),求解可分为两部分(状态转移)
- 不操作:
dp[i][k] = dp[i-1][k]
- 之前有买入:在第 j 天买入,收益就是
prices[i] - (prices[j] - dp[j][k-1])
,我们利用动态规划找第j天(prices[j] - dp[j][k-1])
最小值,即代码中的min
法2:状态机,一共有五种状态:不操作,第一次买入/卖出和第二次买入/卖出。
dp[i][j]表示第i天,状态为j所剩最大现金。
,注:dp[i][j]
中 i表示第i天,j为 [0 - 4] 五个状态- 对于i天都有两个状态:持有和不持有,我们分别考虑前一天的状态。参照122题求解,只不过我们进行两次这样的操作。
代码实现:代码1:动态规划
public int maxProfit(int[] prices) { int n = prices.length; if (prices == null || n == 0) return 0; int K = 2; int[][] dp = new int[n][K + 1]; for (int k = 1; k <= K; k++) { int min = prices[0]; for (int i = 1; i < n; i++) { //找出第 1 天到第 i 天 prices[buy] - dp[buy][k - 1] 的最小值 min = Math.min(prices[i] - dp[i][k - 1], min); //比较不操作和选择一天买入的哪个值更大 dp[i][k] = Math.max(dp[i - 1][k], prices[i] - min); } } return dp[n - 1][K]; }
代码2:状态机
public int maxProfit(int[] prices) { int n = prices.length; if (prices == null || n == 0) return 0; int[][] dp = new int[n][5]; dp[0][1] = dp[0][3] = -prices[0]; for (int i = 1; i < n; ++i) { dp[i][0] = dp[i - 1][0]; dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]); dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]); dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]); } return dp[n - 1][4]; }
Leetcode188,最多买卖K次,问最大收益。
示例 :
输入:k = 2, prices = [2,4,1] 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
思路分析:动态规划:无论买卖多少次,每天我们都对应两个状态。我们这里使用123中状态机的解法:
dp[i][j] 表示第i天,状态为j,所剩下的最大现金是dp[i][j]
。- j的状态表示为:0 表示不操作;1 第一次买入,2 第一次卖出;3 第二次买入,4 第二次卖出... 即除了0之外,奇数状态买入,偶数状态卖出。
- 注意j的步长为2(每天),共进行2*k次买入卖出,注意j循环时不要越界!测试用例中含有0,特判!
状态转移方程为:
dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
- 注意初始条件:当卖出,即
dp[0][j] == 0
, j为偶数;当买入,即dp[0][j] = -prices[0]
,j为奇数。 - 按照定义最终有2k个状态,所以返回
dp[n - 1][2 * k]
。
时间复杂度:O(KN),K为交易的次数。
代码实现:
public int maxProfit(int k, int[] prices) { int n = prices.length; if (prices == null || n == 0) return 0; int[][] dp = new int[n][2 * k + 1]; for (int j = 1; j < 2 * k; j += 2) { dp[0][j] = -prices[0]; } for (int i = 1; i < n; i++) { for (int j = 0; j < 2 * k - 1; j += 2) { dp[i][j + 1] = Math.max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]); dp[i][j + 2] = Math.max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]); } } return dp[n - 1][2 * k]; }
Leetcode309, 可以多次买卖但每次卖出有冷冻期1天。
示例 :
输入: [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
思路分析:动态规划:本题在122基础上加入冷冻期,这里我们第i天分为三种状态,分别用0~2表示:
- 持有股票:可能是前一天就持有或者度过冷冻期又买入(不能直接买入)的最大值。
- 不持有股票,处于冷冻期:原因是当前卖出了股票,得到收益。
- 不持有股票,不在冷冻期:可能前一天在冷冻期或者不在冷冻期的最大值。
注意:最后我们需要返回不持有股票的最大值。
代码实现:
public int maxProfit(int[] prices) { int n = prices.length; if (prices == null || n == 0) return 0; int[][] dp = new int[n][3]; dp[0][0] = -prices[0]; for (int i = 1; i < n; ++i) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]); // 不持有股票 dp[i][1] = dp[i - 1][0] + prices[i]; dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]); } return Math.max(dp[n - 1][1], dp[n - 1][2]); }
Leetcode714, 可以多次买卖,但每次有手续费。
示例 :
输入: 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.
思路分析:本题在122基础上加入手续费,我们只需要在每次卖出时减去手续费即可(即需要支付手续费)!
代码实现:
public int maxProfit(int[] prices, int fee) { int n = prices.length; if (prices == null || n == 0) return 0; int[][] dp = new int[n][2]; dp[0][0] = -prices[0]; dp[0][1] = 0; for (int i = 1; i < n; ++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] - fee); } return dp[n - 1][1]; }