上来直接最难的,后面都是毛毛雨
问题一:买卖股票的最佳时机IV(leetcode188)
问题描述:
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
大体思路:
二维数组dp0[i][j]表示在第i天,最大可交易次数为j时不持有股票的最大收益;
同理dp1[i][j]表示在第天,最大可交易次数为j时持有股票的最大收益。i从0到最大天数,j从0到k。
dp0[i][j] = max(dp0[i - 1][j], dp1[i - 1][j] + prices[i])
dp1[i][j] = max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i])
第一个式子:当天没有股票有两种可能,要么前一天就没持有股票;要么为前一天持有股票,当天卖了。
第二个式子:当天持有股票有两种可能,要么前一天就持有股票,或者前一天没持有股票,当天买入了。
注:这里需要注意的是第二个式子中,第二种情况下的前一天的最大可交易次数为j-1而不为j,这是由于买入的次数要小于最大买入次数,假设为dp0[i - 1][j] 他可能在i-1天之前已经进行过j次交易了,在第i天坑定不能再买入了。
传递函数有了,下面描述其的初值。
我们从传递函数发现,当前值总是与其正上方的值和左上角的值有关,因此只要对dp0和dp1的第一列第一行初始化即可。
dp0的第一列和第一行均为0,首先第一列j=0,由于最大可交易次数为0,那坑定不能买了为0,第一行i= 0,由于在第1天,不持有股票只有一种可能就是没有买所以为0。
dp1的第一列,j= 0最大可交易次数为0,但是还要持有,这是不可能实现的,因此为非法状态统统设为负无穷;第一行(除第一个元素)i = 0,可交易次数大于0了,在第1天想持有股票,只能买第一天的,因此统统设为 -1 * prices[0]。
实现代码如下:
class Solution {
public int maxProfit(int k, int[] prices) {
if(k == 0 || prices.length <= 1){
return 0;
}
if(k > prices.length / 2){
return maxProfit(prices);
}
int[][] dp0 = new int[prices.length][k + 1];
int[][] dp1 = new int[prices.length][k + 1];
/* dp0[i][0] 和 dp0[0][j] 均为0*/
for(int i = 0; i < prices.length; i++){
dp1[i][0] = Integer.MIN_VALUE;
}
for(int j = 1; j < k + 1; j++){
dp1[0][j] = -1 * prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int j = 1; j < k + 1; j++){
dp0[i][j] = Math.max(dp0[i - 1][j], dp1[i - 1][j] + prices[i]);
dp1[i][j] = Math.max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i]);
}
}
return dp0[prices.length - 1][k];
}
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < dp0.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
}
由于k可能很大,当k > n / 2时问题就会转化为不用考虑最大交易次数的情况,可以认为最大交易次数为无穷。后面的代码下一节将会分析。
问题二:最大可交易次数不限(leetcode122)
该问题是问题一中k = 无穷的特例,此时 k = k - 1,因此转移函数变为
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i])
dp1[i] = max(dp1[i - 1], dp0[i - 1] - prices[i])
问题由二维dp转化为一维dp
初值为:d0[0] = 0,没买肯定是0啊, d1[0] = -prices[0],买了肯定是- prices[0]。
实现代码如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < dp0.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
问题三:最大交易次数为1(leetcode121)
dp0[i][j] = max(dp0[i - 1][j], dp1[i - 1][j] + prices[i])
dp1[i][j] = max(dp1[i - 1][j], dp0[i - 1][j - 1] - prices[i])
将上式中的j换成1,得到如下:
dp0[i][1] = max(dp0[i - 1][1], dp1[i - 1][1] + prices[i])
dp1[i][1] = max(dp1[i - 1][1], dp0[i - 1][0] - prices[i])
由于dp0[i - 1][0] = 0,所以最终化简后的转移方程为:
dp0[i]= max(dp0[i - 1], dp1[i - 1]+ prices[i])
dp1[i]= max(dp1[i - 1], 0 - prices[i])
初值:dp0[0] = 0, dp1[0] = -prices[0]
该结果也可以使用自然语言理解,当天不持有的有两种情况,或者前一天就不持有,或者前一天持有当天卖出去了;当天持有的两种情况为:前一天就持有,或者前一天不会持有,当天买了。由于只能买一次,前一天不持有说明之前也没进行过交易,因此前一天不持有的最大收益为0.
实现代码如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];// dp0[i] i天不持有股票的最大利润
int[] dp1 = new int[prices.length]; // 持有股票的最大利润
dp0[0] = 0;
dp1[0] = -1 * prices[0];
for(int i = 1; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], 0 - prices[i]);
}
return dp0[dp0.length - 1];
}
当然由于只交易一次,因此还可以很多其他办法解决,比如可以使用贪心的策略,找到当前点之前最小的元素,即在前面那个位置买,在当前点卖。由于本文主要将dp,这里就不多加赘述了。
问题四:最大交易次数为2(leetcode123)
这问题没啥说的,就令k=2咯,直接搞。
代码如下:
public int maxProfit(int[] prices) {
if(prices.length <= 1){
return 0;
}
int K = 2;
int[][] dp0 = new int[prices.length][K + 1];
int[][] dp1 = new int[prices.length][K + 1];
// dp0[0][k] 和 dp0[i][0] 均为0
for(int i = 0; i < prices.length; i++){ // 第一列 最大允许交易次数为0 还要持有 定为非法
dp1[i][0] = Integer.MIN_VALUE;
}
for(int k = 1; k <= K; k++){ //第一行
dp1[0][k] = -1 * prices[0];
}
for(int i = 1; i < prices.length; i++){
for(int k = 1; k <= K; k++){
dp0[i][k] = Math.max(dp0[i - 1][k], dp1[i - 1][k] + prices[i]);
dp1[i][k] = Math.max(dp1[i - 1][k], dp0[i - 1][k - 1] - prices[i]);
}
}
return dp0[prices.length - 1][K];
}
问题五:买卖股票最佳时间含手续费(leetcode714)
问题描述:
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
大体思路:
直接在k=无穷上改就行,由于每笔交易都含手续费,因此在卖的时候加上手续费就行,转移方程如下:
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i] - fee)
dp1[i] = max(dp1[i - 1], dp0[i - 1] - prices[i])
初值 dp0[0] = 0, dp1[0] = - prices[0]
代码如下:
public int maxProfit(int[] prices, int fee) {
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length];
int[] dp1 = new int[prices.length];
dp1[0] = -1 * prices[0];
for(int i = 1; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i] - fee);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 1] - prices[i]);
}
return dp0[dp0.length - 1];
}
问题六:最佳股票买卖时机含冷冻期(leetcode309)
问题描述:
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
大体思路:
由于当天卖出股票后第二天不能直接买入,也就是说只有前天没持有,昨天没持有,今天才可以进行买入。传递函数如下:
dp0[i] = max(dp0[i - 1], dp1[i - 1] + prices[i])
dp1[i] = max(dp1[i - 1], dp0[i - 2] - prices[i])
由于前天没有持有,昨天也没有持有,今天买入之前的收益是等于前天的收益的。因此为dp0[i - 2] - prices[i]
由于用到i- 2,初值除了赋i= 0,还应对 i = 1赋值。
dp0[0] = 0;
dp1[0] = 0 - prices[0];
dp0[1] = Math.max(dp0[0], dp1[0] + prices[1]);
dp1[1] = Math.max(dp1[0], dp0[0] - prices[1]);// 由于-1时坑定没卖出,因此为dp0[0] - prices[1]。
实现代码如下:
public int maxProfit(int[] prices) {
// return dfs(prices, 0, 0, 0, 0);
if(prices.length <= 1){
return 0;
}
int[] dp0 = new int[prices.length]; //dp0[i] 为i天不持有股票的最大收益
int[] dp1 = new int[prices.length]; //
dp0[0] = 0;
dp1[0] = 0 - prices[0];
dp0[1] = Math.max(dp0[0], dp1[0] + prices[1]);
dp1[1] = Math.max(dp1[0], dp0[0] - prices[1]);
for(int i = 2; i < prices.length; i++){
dp0[i] = Math.max(dp0[i - 1], dp1[i - 1] + prices[i]);
dp1[i] = Math.max(dp1[i - 1], dp0[i - 2] - prices[i]);
}
return dp0[prices.length - 1];
}