714. Best Time to Buy and Sell Stock with Transaction Fee
1. 题目描述
题目链接
Your are given an array of integers prices, for which the i-th element is the price of a given stock on day i; and a non-negative integer fee representing a transaction fee.
You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction. You may not buy more than 1 share of a stock at a time (ie. you must sell the stock share before you buy again.)
Return the maximum profit you can make.
Example 1:
Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
Buying at prices[0] = 1
Selling at prices[3] = 8
Buying at prices[4] = 4
Selling at prices[5] = 9
The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
2. 题目分析
购入购票和售出股票的次数不限,但售出时需要手续费fee,要求,找到获得的最大利润。那么,我们售出的时机,就是不但要求,售出的价值大于(购入时的价值+手续费fee),而且,还要保证没有出售早了。比如说1, 3, 2, 8, 4, 9,但第二天时,满足售出的价值大于(购入时的价值+手续费fee),如果这时候出售,那么第三天2买入的话,第四8出售,那么总利润,(3-1-fee)+(8-2-fee)=4 小于(8-1-fee)=5,说明卖早了。
那么怎样才算出售早了呢?这点应该是最难考虑的地方了,既然日后总有可能存在满足条件售出的价值大于(购入时的价值+手续费fee)的日期,那么我们干脆从另一个角度出发,出售完股票不是还要重新买入股票吗?那我们考虑什么时候适合重新买入股票,就是买入股票的前一天i-1的股票价值大于(第i天购入时的价值+手续费fee),那么就可以在第i-1天出售这股票,然后在第i天重入购入股票。
3. 解决思路
- 贪心算法,上面的题目分析是一种贪心算法的思路,每次在局部找到最优解。我也不确定,毕竟这思路不是我自己想出来的,当时虽然找到了这个条件:售出的价值大于(购入时的价值+手续费fee),但一直没找到出售早了的限定条件,所以就去看别人的解题思路。。。但这想法好像是有点贪心算法的味道。
- 动态规划:
对于第i天的收益,可能有两种情况:手中有stock和手中没有stock
如果手中没有stock,可能是今天刚卖出或者是保持前一天的状态 ,此时令收益为sold。
如果手中有stock,可能是今天刚买入或者是保持前一天的状态,此时令收益为hold(收益有可能是负的)。
因为第i天的状态仅和前一天的状态有关,所以只需要两个数组sold和hold。
sold表示手上没有stock状态下的收益,hold表示手上有stock状态下的收益。
解释如下:
sold 该天结束手里没有股票的情况下,已经获得的最大收益;
hold 该天结束手里有股票的情况下,已经获得的最大收益。
所以转移状态分析如下:
sold更新的策略是:既然今天结束之后手里没有股票,那么可能是今天没买(保持昨天的状态),也可能是今天把股票卖出了;
hold 更新的策略是:今天今天结束之后手里有股票,那么可能是今天没卖(保持昨天的状态),也可能是今天买了股票。
这里需要注意sold和hold的初始值,最终输出sold,因为最后一天的情况一定是手里没有stock的。
4. 代码实现(java)
- 贪婪算法
package com.algorithm.leetcode.dynamicAllocation;
/**
* Created by 凌 on 2018/12/15.
* 描述:714. Best Time to Buy and Sell Stock with Transaction Fee
*/
public class MaxProfit_upgradedVersion {
public static void main(String[] args) {
// int[] prices = {1, 3, 2, 8, 4, 9};
int[] prices = {1, 3, 2, 8,9,4};
int fee = 2;
int result = maxProfit_greedy(prices,fee);
System.out.println(result);
}
/**
* 贪心算法
* @param prices
* @param fee
* @return
*/
public static int maxProfit_greedy(int[] prices, int fee) {
int profit = 0;
int curProfit = 0;
int minP = prices[0];
int maxP = prices[0];
for (int i = 1; i < prices.length; i++) {
minP = Math.min(minP, prices[i]);
maxP = Math.max(maxP, prices[i]);
curProfit = Math.max(curProfit, prices[i] - minP - fee);
//当买入股票的前一天i-1的股票价值大于(第i天购入时的价值+手续费fee),那么就可以在第i-1天出售这股票,然后在第i天重入购入股票
if ((maxP - prices[i]) >= fee) {
profit += curProfit;
curProfit = 0;
minP = prices[i];
maxP = prices[i];
}
}
//curProfit记录了当前一次交易能得到的最大收益,只有当maxP-prices[i]>=fee时,才将curProfit累加到总的收益中。
// 最后一次交易不需要考虑是否早卖了,所以直接累加最后一次的curProfit。
return profit + curProfit;
}
}
- 动态规划算法
/**
* 动态规划
* @param prices
* @param fee
* @return
*/
public static int maxProfit_dp(int[] prices, int fee) {
if (prices == null || prices.length < 2){
return 0;
}
int len = prices.length;
/**
* 第i天,如果手中有stock
* hold[i]表示第i天,你保持这份股票,你可以获得的最大利润,
* 第i天有两种决定,继续保持这份股票,买入这份股票;
* 如果第i天保持这股票,那么hold[i] = hold[i-1],
* 如果第i天买入这股票(说明前一天已经出售这只股票了),那么hold[i] = sold[i-1] - prices[i]
* hold[i] = max(hold[i-1], sold[i-1] - prices[i])
*/
int[] hold = new int[len];
/**
* 第i天,如果手中没有stock
* sold[i]表示第i天,你出售这份股票,你可以获得的最大利润
* 第i天有两种决定,继续保持这份股票,出售这份股票;
* 如果第i天保持这股票,那么sold[i] = sold[i-1],
* 如果第i天出售这股票,那么sold[i] = hold[i-1] + prices[i] - fee
* sold[i] = max(sold[i-1], hold[i-1] + prices[i] - fee)
*/
int[] sold = new int[len];
hold[0] = -prices[0];
for (int i = 1; i < len; ++i) {
hold[i] = Math.max(hold[i - 1], sold[i - 1] - prices[i]);
sold[i] = Math.max(sold[i - 1], hold[i - 1] + prices[i] - fee);
}
return sold[len - 1];
}