参考网址:https://leetcode-cn.com/problemset/all/?search=%E8%82%A1%E7%A5%A8
题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 当你可以进行k笔交易时,求可以获取的最大利润。
输入: [7,1,5,3,6,4] k=1
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
输入: [7,6,4,3,1] =1
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
难度介绍
Level 1, k = 1。 我们只能进行一笔交易。 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
Level 2, k = 任意值. 我们可以进行任意笔买卖 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
Level 3, k = 2, 我们可以进行两笔交易 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/
Level 4, k 需要指定。 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/
解析
1、常规解法
我们需要找到买入和卖出差值最大的区间(之和),且买入时间必须在卖出时机之前。首先从数组中截取股票价格上升和下降的区间,计算出上升和下降的差值,将上升和下降的差值分别存在upTrendList
和downTrendList
中,如果股票开始的时候或者结束的时候处于下降趋势,那么这两段的差值不具有任何意义,所以在downTrendList
中剔除掉。需要保证:upTrendList
里面的元素永远比downTrendList
多一个:
Example:
prices = [5,2,3,2,6,6,2,9,1,0,7,4,5,0]
upTrendList = [3-2, 6-2, 9-2, 7-0, 5-4] = [1, 4, 7, 7, 1]
downTrendList = [3-2, 6-2, 9-0, 7-4] = [1, 4, 9, 3]
当前上升区间有5个数组,此时如果k >= 5,我们可以在股票价格上升之前买入,在价格下降之前卖出,即可获得最大利润。
result = sum(upTrendList);
如果k < 5, 我们则需要对upTrendList
和downTrendList
进行缩减,使得upTrendList.size() = k
。
方法如下:
1、找到upTrendList 和 downTrendList 中的最小值,
1.1如果最小值在upTrendList中,我们需要抛弃这个元素,同时,删除这个元素对应两边的下降值,更新成一个总下降值并插入回
downTrendList
的原有位置,如果是两边的边界值则可以直接删除:
Example:
upTrendList = [1, 4, 7, 7, 1]
downTrendList = [1, 4, 9, 3]
min value = 1, index = 0, in upTrend List
upTrendList = [4, 7, 7, 1]
downTrendList = [4, 9, 3]
min value = 1, index = 3, in upTrend List
upTrendList = [4, 7, 7]
downTrendList = [4, 9]
if index is in middle:
newValue
=downTrendList[i-1] + downTrendList[i] - upTrendList[i]
downTrendList.remove(i)
downTrendList.remove(i-1)
downTrendList.add(i-1, newValue)
Example:
upTrendList = [2, 1, 5]
downTrendList = [3, 4]
min value = 1, index = 1, in upTrend List
upTrendList = [2, 5]
downTrendList = [3+6-1] = [6]
1.2 如果最小值在downTrendList中, 我们需要将他两边的upTrend整合在一起,并在downTrend中删除掉这个值
upTrendList = [4, 3, 6]
downTrendList = [6, 2]
min value = 2, index = 1, in downTrend List
upTrendList = [4, 3,+6-2] = [4, 7]
downTrendList = [6]
- 重复步骤1,直到
upTrendList.size() == k
代码实现逻辑如下:
public int maxProfit(int k, int[] prices) {
int size = prices.length;
if (size <= 1 || k == 0) {
return 0;
}
List upList = new ArrayList<>();
List downList = new ArrayList<>();
int low = prices[0];
int high = -1;
for(int i = 1; i < size; i++) {
if (prices[i] > prices[i-1]) {
if (i+1 == size || prices[i+1] <= prices[i]) {
upList.add(prices[i] - low);
if (high >= 0) {
downList.add(high-low);
}
high = prices[i];
}
}
else {
low = prices[i];
}
}
System.out.println("Up list: " + upList);
System.out.println("Down list: " + downList);
while(k < upList.size()) {
System.out.println("k = " + k + ", upList size: " + upList.size());
int minFromUp = upList.stream().mapToInt(Integer::intValue).min().getAsInt();
int minFromDown = downList.stream().mapToInt(Integer::intValue).min().getAsInt();
// System.out.println("Min from up: " + minFromUp + ", min from down: " + minFromDown);
if (minFromUp <= minFromDown) {
int index = upList.indexOf(minFromUp);
if (index == 0) {
downList.remove(0);
} else if (index == upList.size()-1) {
downList.remove(downList.size()-1);
} else {
int newValue = downList.get(index-1) + downList.get(index) - upList.get(index);
// System.out.println("New value for down: " + newValue);
downList.remove(index);
downList.remove(index-1);
downList.add(index-1, newValue);
}
upList.remove(index);
} else {
int index = downList.indexOf(minFromDown);
int newValue = upList.get(index) + upList.get(index+1) - downList.get(index);
// System.out.println("New value for Up: " + newValue);
upList.remove(index+1);
upList.remove(index);
downList.remove(index);
upList.add(index, newValue);
}
System.out.println("Up list: " + upList);
System.out.println("Down list: " + downList);
}
return upList.stream().mapToInt(Integer::intValue).sum();
}
2、递归解法
设置 dp[a][b]
其中 a = 第a天
b = 0 ,1, 2 。。。。 2k
其中0表示还没有买卖,1 = 第一次买入, 2 = 第一次卖出。。。。 2i - 1 = 第i次买入,2i = 第i次卖出
确定递推公式
需要注意:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区。
达到dp[i][1]状态,有两个具体操作:
- 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
- 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][0]
那么dp[i][1]究竟选 dp[i-1][0] - prices[i],还是dp[i - 1][0]呢?
一定是选最大的,所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][0]);
同理dp[i][2]也有两个操作:
- 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][i] + prices[i]
- 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
所以dp[i][2] = max(dp[i - 1][i] + prices[i], dp[i][2])
for (int i = 1;i < prices.length; 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]);
}
}