递归问题-股票最佳买入卖出时机

参考网址: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、常规解法
我们需要找到买入和卖出差值最大的区间(之和),且买入时间必须在卖出时机之前。首先从数组中截取股票价格上升和下降的区间,计算出上升和下降的差值,将上升和下降的差值分别存在upTrendListdownTrendList中,如果股票开始的时候或者结束的时候处于下降趋势,那么这两段的差值不具有任何意义,所以在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, 我们则需要对upTrendListdownTrendList进行缩减,使得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. 重复步骤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]);
            }
        }

你可能感兴趣的:(递归问题-股票最佳买入卖出时机)