LeetCode买卖股票问题汇总

本文对LeetCode中的买卖股票问题做了一个汇总。

121. Best Time to Buy and Sell Stock

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

方法1

假设当前在第 i 天,令 minPrice 表示前 i-1 天的最低价格;令 maxProfit 表示前 i-1 天的最大收益。那么考虑第 i 天的收益时,存在两种情况:

  • 在第 i 天卖出。很显然,想要获得最大收益,应该在前 i-1 天中价格最低的时候买入,即此时的收益为:prices[i] - minPrice。(可能会出现负数,但是没关系)
  • 不在第 i 天卖出。那么第 i 天的最大收益就等于前 i -1 天中的最大收益

状态转移方程为

第 i 天最大收益 = max( 在第 i 天卖出的所得收益 , 前 i-1 天的最大收益)

代码实现如下:

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
    public int maxProfit(int[] prices) {
        if(prices == null || prices.length == 0) return 0;

        int maxProfit = 0, minPrice = prices[0];
        for(int i = 1; i < prices.length; i++) {
      	    maxProfit = Math.max(maxProfit, prices[i]-minPrice);
            minPrice = Math.min(minPrice, prices[i]);
        }
        return maxProfit;
    }
}

方法2:kadane算法

这个解法是从原文作者的链接中找到的解法,该解法使用了kadane's algorithm ,该算法用于解决最大连续子序列问题,可以了解一下。

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
    public int maxProfit(int[] prices) {
      int maxCur = 0, maxSoFar = 0;
      for(int i = 1; i < prices.length; i++) {
             maxCur = Math.max(0, maxCur += prices[i] - prices[i-1]);
             maxSoFar = Math.max(maxCur, maxSoFar);
         }
         return maxSoFar;
    }
}  

根据这一解法,我们其实也可以发现,第121题还可以理解为Maximum Subarray问题。

假设给出的股票价格数组prices = {7, 1, 5, 3, 6, 4},我们先计算出第 i 天和第 i-1 天的差值,即 diff = {-6, 4, -2, 3, -2} ,那么计算prices数组完成一笔交易的最大值就等价于在计算diff数组的具有最大和的连续子数组。在这里,就是 4+(-2)+3 = 5, 即prices[4] - prices[1] = 5。

prices = {prices[0], prices[1], prices[2], prices[3], prices[4]}
diff   = {prices[1]-prices[0], prices[2]-prices[1], prices[3]-prices[2], prices[4]-prices[3]}
假设在prices中,最大差值:
maxProfit = prices[3] - prices[0]
那么在diff数组中,对应的值:
maxProfit = (prices[1]-prices[0]) + (prices[2]-prices[1]) + (prices[3]-prices[2])
          = prices[3] - prices[0]

因此,本题可以转化为求最大连续子序列的和的问题。

122. Best Time to Buy and Sell Stock II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

方法1:有限状态机的思想

(注:本题的解法是在做了309题之后,开始尝试用状态机的思想重新思考本题,完全自己解出来了。终于不需要借助题解,独立完成了,说明自己是真的懂的了开心

考虑到对于任意一天,要么持有股票(定义该状态为 hold),要么不持有股票(定义该状态为 sold)。

  • 假设第 i 天为 hold 状态:

    • case 1:可能前一天是 hold 状态,即在第 i-1 天持有股票,在第 i 天时继续持有,啥也不干(我们定义“啥也不干”这一动作为 rest);
    • case 2:可能前一天是 sold 状态,即在第 i-1天本来是不持有股票的,但是在第 i 天买入了一支新股,因此第 i 天变成了 hold 状态。
  • 假设第 i 天为 sold 状态:

    • case 1:可能前一天是 hold 状态,即在第 i-1 天持有股票,但是在第 i 天时抛售了,于是变成了 sold 状态;
    • case 2:可能前一天是 sold 状态,即在第 i-1天不持有股票的,在第 i 天继续啥也不干。

基于上面的分析,可以画出如下状态机转换示意图(原创图,ppt画的):

上面是理论分析,落实到代码层面,我们定义变量holdsold表示第 i 天结束后所拥有的收益。很显然,最后的结果保存在sold中,即卖完手中的股票总比还持有股票要多套现一点钱的。根据状态机,我们可以写出如下状态状态转移方程:

假设当前是第 i 天
hold = max(prev_hold, prev_sold - prices[i]);
sold = max(prev_sold, prev_hold + prices[i]);

代码实现如下:

// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
      public int maxProfit(int[] prices) {
            if(prices.length == 0) return 0;
    	    int sold = 0, hold = -prices[0]; // 初始化
            for(int i = 1; i < prices.length; i++) {
      	        int prev_sold = sold;
        	int prev_hold = hold;
        	hold = Math.max(prev_hold, prev_sold - prices[i]);
        	sold = Math.max(prev_sold, prev_hold + prices[i]);
    	    }
            return sold;
      }
}  

方法2:峰谷法

非原创,参考这里

ProfitGraph

代码实现:

class Solution {
     public int maxProfit(int[] prices) {
        int i = 0;
        int valley = prices[0];
        int peak = prices[0];
        int maxprofit = 0;
        while (i < prices.length - 1) {
            while (i < prices.length - 1 && prices[i] >= prices[i + 1])
                i++;
            valley = prices[i];
            while (i < prices.length - 1 && prices[i] <= prices[i + 1])
                i++;
            peak = prices[i];
            maxprofit += peak - valley;
        }
        return maxprofit;
    }
} 

方法3:在方法2的基础上进一步简化

class Solution {
    public int maxProfit(int[] prices) {
        int maxprofit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1])
                maxprofit += prices[i] - prices[i - 1];
        }
        return maxprofit;
    }
}

123. Best Time to Buy and Sell Stock III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

方法1:基于188题的做法

// 时间复杂度:O(k*n)
// 空间复杂度:O(k*n)
class Solution {
    public int maxProfit(int[] prices) {
        return maxProfitWithK(2, prices);
    }

    public int maxProfitWithK(int k, int[] prices) {
        int n = prices.length;
        if(n == 0) return 0;

        int[][] dp = new int[k+1][n];
        for(int i = 1; i <= k; i++) {
            int maxDiff = dp[i-1][0] - prices[0];
            for(int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i][j-1], prices[j] + maxDiff);
                maxDiff = Math.max(maxDiff, dp[i-1][j]-prices[j]);
            }
        }
        return dp[k][n-1];
    }
}

方法2:状态机的思想

70b91be6eda6bb3b50a697cae0a26246aa78a7ae0f9c5045d18eaef81a4556ef

代码实现如下:

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length == 0) return 0;
        int hold_1 = -prices[0], sold_1 = 0;
        int hold_2 = -prices[0], sold_2 = 0;
        for(int i = 1; i < prices.length; i++) {
            hold_1 = Math.max(hold_1, -prices[i]);
            sold_1 = Math.max(sold_1, hold_1 + prices[i]);
            hold_2 = Math.max(hold_2, sold_1 - prices[i]);
            sold_2 = Math.max(sold_2, hold_2 + prices[i]);
        }
        return sold_2;
    }
}

188. Best Time to Buy and Sell Stock IV

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
     随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

本题参考https://www.youtube.com/watch?v=oDhu5uGq_ic,非常感谢这位印度小哥,你帮我给讲懂了!

方法1:O(k*n^2)

// 时间复杂度:O(k*n^2)
// 空间复杂度:O(k*n)
class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if(n == 0) return 0;
      
        int[][] dp = new int[k+1][n];
        for(int i = 1; i <= k; i++) {
            for(int j = 1; j < n; j++) {
                int maxVal = 0;
                for(int m = 0; m < j; m++) {
                    maxVal = Math.max(maxVal, prices[j]-prices[m]+dp[i-1][m]);
                }
                dp[i][j] = Math.max(dp[i][j-1], maxVal);
            }
        }
        return dp[k][n-1];
    }
}

方法2:O(k*n)

// 时间复杂度:O(k*n)
// 空间复杂度:O(k*n)
class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if(n == 0) return 0;

        int[][] dp = new int[k+1][n];
        for(int i = 1; i <= k; i++) {
            int maxDiff = dp[i-1][0] - prices[0];
            for(int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i][j-1], prices[j] + maxDiff);
                maxDiff = Math.max(maxDiff, dp[i-1][j]-prices[j]);
            }
        }
        return dp[k][n-1];
    }
}  

方法3:解决内存超限的问题

优化点,当k >= prices.length/2 时,表示可以把所有可能的交易都执行一遍,此时的情况就退回成了股票系列的第122题了。

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if(n == 0) return 0;
        if(k >= n/2) return maxProfitWithGreedy(prices);

        int[][] dp = new int[k+1][n];
        for(int i = 1; i <= k; i++) {
            int maxDiff = dp[i-1][0] - prices[0];
            for(int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i][j-1], prices[j] + maxDiff);
                maxDiff = Math.max(maxDiff, dp[i-1][j]-prices[j]);
            }
        }
        return dp[k][n-1];
    }

    private int maxProfitWithGreedy(int[] prices) {
        int maxProfit = 0;
        for(int i = 1; i < prices.length; i++) {
            if(prices[i] - prices[i-1] > 0) {
                maxProfit += prices[i] - prices[i-1];
            }
        }
        return maxProfit;
    }
}

309. Best Time to Buy and Sell Stock with Cooldown

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

输入: [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

方法1:有限状态机的思想

参考https://www.youtube.com/watch?v=oL6mRyTn56M

代码实现如下:

class Solution {
    public int maxProfit(int[] prices) {
        int hold = Integer.MIN_VALUE, sold = 0, rest = 0;
        for(int i = 0; i < prices.length; i++) {
            int prev_rest = rest;
            int prev_hold = hold;
            rest = Math.max(rest, sold);        
            hold = Math.max(hold, prev_rest - prices[i]);
            sold = prev_hold + prices[i];
        }
        return Math.max(sold, rest);
    }
}

714. Best Time to Buy and Sell Stock with Transaction Fee

给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。

可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

方法1:有限状态机的思想(几乎等同于第122题)

注意初始化和状态转移时要考虑收付费,其他的思路完全一样。不再赘述。

class Solution {
    public int maxProfit(int[] prices, int fee) {
        if(prices.length == 0) return 0;
        int sold = 0, hold = -prices[0]-fee; // 初始化
        for(int i = 1; i < prices.length; i++) {
            int prev_sold = sold;
            int prev_hold = hold;
            hold = Math.max(prev_hold, prev_sold - prices[i] - fee);
            sold = Math.max(prev_sold, prev_hold + prices[i]);
        }
        return sold;
    }
}

方法2:DP思想

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int[][] dp = new int[n+1][2];
        dp[0][0] = 0;
        dp[0][1] = Integer.MIN_VALUE;
        for(int i = 1; i <= n; i++) {
            /* 
            1.在第 i 天结束时,不持有股票
                - 在第 i-1 天时就不持有股票
                - 在第 i-1 天时持有股票,而在第 i 天卖出
            */
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
            /*
            2.在第 i 天结束时,持有股票
                - 在第 i-1 天时就持有股票
                - 在第 i-1 天时不持有股票,在第 i 天买入股票(在买入股票时还需扣除手续费)
            */
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1] - fee);
        }
        return dp[n][0];
    }
}
posted @ 2020-07-07 09:44  kkbill  阅读(3402)  评论(1编辑  收藏  举报