假设你有一个数组,其中第 i 个元素是第 i 天给定股票的价格。
设计一个算法来找到最大的利润。您最多可以完成 k 笔交易。
注意:
你不可以同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
这道题不像前面的不限交易次数,要求交易次数在k次以内,那么就不能有获利就交易,选择交易时机比较关键,比如[2,3,1,2,6,2,6,7],限制交易2次,如果取到[1,7],那么就漏掉了中间两个[2,6,2,6],就不是最佳方案。所以还是应该尝试动态规划从局部最优找到全局最优。
此题最难的感觉还是写出递推方程,最终还是参考其他博客的递推方程:
mustSell[i][j]表示前i个交易日最多交易j次能达到的最大盈利,其中第i日必须进行了至多第j次交易。
globalSell[i][j]表示前i个交易日最多交易j次能达到的最大盈利,到第i日必须完成了至多第j次交易
递推方程如下:
int profit = prices[i] - prices[i-1];
profit = profit >= 0 ? profit : 0;
mustSell[i][j]= Math.max(globalSell[i-1][j-1] + profit,mustSell[i-1][j] + prices[i] - prices[i-1]);
globalSell[i][j] = Math.max(globalSell[i-1][j],mustSell[i][j]);
profit 表示第i天买入,第i-1天卖出的盈利情况,如果profit<0,则profit=0
mustSell[i][j]分为两种情况:
1.mustSell[i][j] = globalSell[i-1][j-1] + profit
前i-1天最多进行交易j-1次,剩下一次完整的交易留给你,就看你在第i天的表现了,必须进行,那么这时如果还要交易,可以第i-1再买,第i卖,但是这有可能是亏钱的,如果是亏钱,那就第i天买同时第i天卖,好过亏钱,所以有profit = max(0,prices[i] - prices[i-1]);
2.mustSell[i][j] = mustSell[i-1][j] + prices[i] - prices[i-1]
前i-1天最多进行交易j次,且第i-1天必须进行第j次交易,而又有mustSell[i][j],那么意思第i-1天买、第i天卖,都是进行了第j次交易,因此有 prices[i] - prices[i-1]
globalSell[i][j] = Math.max(globalSell[i-1][j],mustSell[i][j])
这个就比较好理解:要么第i-1天完成第j次交易就是最佳策略了;要么就是第i天有进行交易能让结果更优。
public int maxProfit(int k, int[] prices) {
if(k < 1){
return 0;
}
if(prices.length < 2){
return 0;
}
int len = prices.length;
if(k*2 >= len-1){//买卖次数足够多
return maxProfit0(prices);
}
if(k == 2){
return maxProfit2(prices);
}
int mustSell[][] = new int [len][k+1];
int globalSell[][] = new int [len][k+1];
for(int j=1;j<=k;j++){
for(int i=1;i<len;i++){//交易k次
int profit = prices[i] - prices[i-1];
profit = profit >= 0 ? profit : 0;
mustSell[i][j] = Math.max(globalSell[i-1][j-1] + profit,mustSell[i-1][j] + prices[i] - prices[i-1]);
globalSell[i][j] = Math.max(globalSell[i-1][j],mustSell[i][j]);
}
}
return globalSell[prices.length-1][k];
}
public int maxProfitk(int dp[][],int k,int start,int count) {
if(k == 0 || start >= dp.length - 1){
return count;
}
int result = 0;
for(int i=start;i<dp.length;i++){
for(int j=i+1;j<dp.length;j++){
if(dp[i][j] > 0){
int temp = maxProfitk(dp,k-1,j+1,dp[i][j] + count);
result = temp > result ? temp : result;
}
}
}
return result;
}
public int maxProfit0(int[] prices) {
if(prices.length < 2){
return 0;
}
int len = prices.length;
int result = 0;
for(int i=1;i<len;i++){
if(prices[i] > prices[i-1]){
result += prices[i] - prices[i-1];
}
}
return result;
}
public int maxProfit2(int[] prices) {
int len = prices.length;
if(len<=1)return 0;
int max=0;
int dp1[] = new int[len];
int curmin= prices[0];
for(int i=1;i<len;i++){
max = max < prices[i] - curmin ? prices[i] - curmin : max;
curmin = prices[i] < curmin ? prices[i] : curmin;
dp1[i] = max;
}
int curmax= prices[len-1];
max=0;
int dp2[] = new int[len];
for(int i=len-1;i>=0;i--){
max = max < curmax-prices[i] ? curmax-prices[i] : max;
curmax = prices[i] > curmax ? prices[i] : curmax;
dp2[i] = max;
}
max=0;
for(int i=len-1;i>0;i--){
max = max < dp1[i]+dp2[i] ? dp1[i]+dp2[i] : max;
}
return max;
}