LeetCode ---- 买卖股票系列问题思路与题解

Posted TheWhc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode ---- 买卖股票系列问题思路与题解相关的知识,希望对你有一定的参考价值。

股票问题

121. 买卖股票的最佳时机

(只能买卖一次)

贪心法

/**
 *  思路: 贪心法
 *  1. 左边选择买入最小值
 *  2. 循环遍历每个元素当成当天卖出,与买入最小值相减,并与结果利润最大值进行比较
 *
 *  时间: O(n)
 *  空间: O(1)
 */
public int maxProfit(int[] prices) 
   int leftMin = Integer.MAX_VALUE;
   int res = 0;
   for (int i = 0; i < prices.length; i++) 
      if(prices[i] < leftMin) 
         leftMin = prices[i];
      
      res = Math.max(res, prices[i] - leftMin);
   
   return res;

动态规划

/**
 *  思路: 动态规划
 *  (一次股票买卖问题)
 *  1. 确定dp数组以及下标含义
 *     dp[i][j] : 表示第i天处于j状态的最大值利润值 (j值只有0和1, 0代表持有股票(即买入), 1代表不持有股票(即卖出))
 *  2. 确定递推公式
 *     dp[i][0] = Math.max(dp[i-1][0], -prices[i])
 *                 第i-1天就持有股票保持现状; 第i天买入股票
 *     dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0])
 *                 第i-1天就不持有股票保持现状; 第i天卖出股票 + 第i-1天持有股票的最大值(即买入的最小值,在dp数组中,持有股票是按负数计算的)
 * 3. 初始化
 *     从上面的递推公式可以看出,初始状态有两个,dp[0][0]和dp[0][1]
 *    dp[0][0]    = -prices[0]
 *    dp[0][1] = 0
 * 4. 确定遍历顺序
 *     从前往后遍历
 *
 *  5. 举例推导dp数组
 *
 *  时间: O(n)
 *  空间: O(n)
 */
public int maxProfit(int[] prices) 
   int[][] dp = new int[prices.length][2];

   dp[0][0] = -prices[0];
   dp[0][1] = 0;

   for (int i = 1; i < prices.length; i++) 
      // 持有股票
      dp[i][0] = Math.max(dp[i-1][0], -prices[i]);
      // 不持有股票
      dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]);
   

   return dp[prices.length-1][1];

动态规划优化

// 在前面动态规划的基础进行空间优化,从二维降为一维
public int maxProfit(int[] prices) 
   int[] dp = new int[2];

   dp[0] = -prices[0];
   dp[1] = 0;

   for (int i = 1; i < prices.length; i++) 
      // 持有股票
      dp[0] = Math.max(dp[0], -prices[i]);
      // 不持有股票
      dp[1] = Math.max(dp[1], prices[i] + dp[0]);
   

   return dp[1];

122. 买卖股票的最佳时机II

(可以买卖多次)

贪心法

/**
 *  思路: 贪心法
 *
 *  贪心策略:
 *     局部最优:收集每天的正利润
 *      全局最优:求得最大利润
 *
 *  将最终利润进行拆解
 *  比如prices[3] - prices[0]
 *  (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
 *  像这样利润分解为每天为单位的维度,不是从第0天到第3天整体考虑
 *  由此推出 (prices[i] - prices[i-1]) + (prices[i-1] - prices[i-2]) + .....(prices[1] - prices[0])
 *
 *  举例:  7  1  5 10  3  6  4
 *          -6  4  5 -7  3  -2
 *  第一天不纳入考虑,因为第一天没有利润
 *
 *  时间: O(n)
 *  空间: O(1)
 */
public int maxProfit(int[] prices) 
   int result = 0;
   for (int i = 1; i < prices.length; i++) 
      if(prices[i] - prices[i-1] > 0) 
         result += prices[i] - prices[i-1];
      
   
   return result;

动态规划

/**
 *  思路: 动态规划
 *
 *  1. 确定dp数组以及下标含义
 *     dp[i][j]:表示第i天状态为j的时候,多次买卖后的最大利润值
 *  2. 确定递推公式
 *
 *     持有股票
 *     dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1])
 *                 第i-1天持有股票保持现状      第i天买入股票 + 第i-1天不持有股票(与一次买卖不同的地方,因为这里存在多次买卖,所以要加上第i-1天不持有股票的利润)
 *
 *      不持有股票
 *      dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]
 *              第i-1天不持有股票保持现状   第i天卖出股票 + 第i-1天持有股票的最大值(即买入的最小值,在dp数组中,持有股票是按负数计算的)
 *  3. 初始化
 *     dp[0][0] = -prices[0]
 *      dp[0][1] = 0
 *  4. 遍历顺序
 *      从前往后遍历
 *
 * 时间: O(n)
 * 空间: O(n)
 */
public int maxProfit(int[] prices) 
   int[][] dp = new int[prices.length][2];

   dp[0][0] = -prices[0];
   dp[0][1] = 0;

   for (int i = 1; i < prices.length; i++) 
      // 持有股票,与一次买卖唯一不同的地方的递推公式上
      dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1]);
      // 不持有股票
      dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]);
   

   return dp[prices.length-1][1];

动态规划优化

// 动态规划 + 空间优化降为一维
// 时间: O(n)
// 空间: O(1)
public int maxProfit(int[] prices) 
   int[] dp = new int[2];

   dp[0] = -prices[0];
   dp[1] = 0;

   for (int i = 1; i < prices.length; i++) 
      // 持有股票,与一次买卖唯一不同的地方的递推公式上
      dp[0] = Math.max(dp[0], -prices[i] + dp[1]);
      // 不持有股票
      dp[1] = Math.max(dp[1], prices[i] + dp[0]);
   

   return dp[1];

123. 买卖股票的最佳时机III

(最多买卖两次)

动态规划

/**
 *  思路: 动态规划
 *  1. 确定dp数组以及下标含义
 *     dp[i][j] : 表示第i天状态为j时,所获得的最大利润
 *     j的范围是0~4, dp[i][0]表示不操作, dp[i][1]表示第一次买入, dp[i][2]表示第一次卖出, dp[i][3]表示第二次买入, dp[i][4]表示第二次卖出
 *  2. 确定递推公式
 *     dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
 *     dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
 *      dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
 *      dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
 *  3. 初始化
 *     dp[0][0] = 0
 *      dp[0][1] = -prices[0]
 *      dp[0][2] = 0
 *      dp[0][3] = -prices[0]
 * 4. 遍历顺序
 *      从前往后遍历
 *
 *  时间: O(n)
 *  空间: O(n)
 */
public int maxProfit(int[] prices) 
   int[][] dp = new int[prices.length][5];

   dp[0][1] = -prices[0];
   dp[0][3] = -prices[0];

   for (int i = 1; i < prices.length; i++) 
      dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
      dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
      dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
      dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
   

   return dp[prices.length-1][4];

动态规划优化

// 动态规划 + 优化空间
// 时间: O(n)   空间: O(1)
public int maxProfit(int[] prices) 
   int[] dp = new int[5];

   dp[1] = -prices[0];
   dp[3] = -prices[0];

   for (int i = 1; i < prices.length; i++) 
      dp[1] = Math.max(dp[1], -prices[i] + dp[0]);
      dp[2] = Math.max(dp[2], prices[i] + dp[1]);
      dp[3] = Math.max(dp[3], -prices[i] + dp[2]);
      dp[4] = Math.max(dp[4], prices[i] + dp[3]);
   

   return dp[4];

188. 买卖股票的最佳时机IV

(最多买卖k次)

动态规划

/**
 *  思路: 动态规划
 *  1. 确定dp数组以及下标含义
 *     dp[i][j] : 表示第i天状态为j时,所获得的最大利润
 *     j的范围是0~2*k, dp[i][0]表示不操作, dp[i][1]表示第一次买入, dp[i][2]表示第一次卖出, dp[i][3]表示第二次买入, dp[i][4]表示第二次卖出
 *     所以很容易看出来, 奇数就是买入, 偶数就出卖出
 *  2. 确定递推公式
 *     dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
 *     dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
 *      dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
 *      dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
 *  3. 初始化
 *     dp[0][0] = 0
 *      dp[0][1] = -prices[0]
 * 4. 遍历顺序
 *      从前往后遍历
 *
 *  时间: O(n * k)
 *  空间: O(n * k)
 */
public int maxProfit(int k, int[] prices) 
   if(prices == null || prices.length == 0) 
      return 0;
   

   int[][] dp = new int[prices.length][2 * k + 1];

   for (int j = 1; j < 2 * k + 1; j += 2) 
      dp[0][j] = -prices[0];
   

   for (int i = 1; i < prices.length; i++) 
      for (int j = 1; j < 2 * k + 1; j += 2) 
         // j为奇数时,表示买入
         dp[i][j] = Math.max(dp[i-1][j], -prices[i] + dp[i-1][j-1]);
         // j为偶数时,表示卖出
         dp[i][j+1] = Math.max(dp[i-1][j+1], prices[i] + dp[i-1][j]);
      
   

   return dp[prices.length-1][2*k];

动态规划优化

// 动态规划 + 空间优化到一维
// 时间: O(n * k) 空间: O(k)
public int maxProfit(int k, int[] prices) 
   if(prices == null || prices.length == 0) 
      return 0;
   

   int[] dp = new int[2 * k + 1];

   for (int j = 1; j < 2 * k + 1; j += 2) 
      dp[j] = -prices[0];
   

   for (int i = 1; i < prices.length; i++) 
      for (int j = 1; j < 2 * k + 1; j += 2) 
         // j为奇数时,表示买入
         dp[j] = Math.max(dp[j], -prices[i] + dp[j-1]);
         // j为偶数时,表示卖出
         dp[j+1] = Math.max(dp[j+1], prices[i] + dp[j]);
      
   

   return dp[2*k];

309. 最佳买卖股票时机含冷冻期

(买卖多次,卖出有一天冷冻期)

动态规划

/**
 *  思路: 动态规划
 *  1. 确定dp数组以及下标含义: dp[i][j]表示第i天状态为j时,所获得的最大利润值
 *     j的范围是0~3
 *     dp[i][0]:状态一 表示买入股票状态(今天买入, 之前买入了股票然后没有操作保持到今天还是买入状态)
 *                           今天买入又分两种情况:
 *                           - 前一天是冷冻期(状态四)
 *                           - 前一天是保持卖出的状态(状态二)
 *      dp[i][1]:状态二 表示保持卖出股票状态(两天前就已卖出了股票, 度过了冷冻期, 一直没有操作保持到今天还是卖出状态)
 *                  分两种情况:
 *                  - 前一天是卖出(状态二)
 *                  - 前一天是冷冻期(状态四)
 *      dp[i][2]:状态三 表示今天是卖出股票状态(今天卖出)
 *                  - 昨天一定是买入的状态(状态一)
 *      dp[i][3]:状态四 表示今天为冷冻期,冷冻期只有一天
 *                  - 昨天一定是卖出的状态(状态三)
 *
 *      为什么要把卖出拆为两个状态(状态二和状态三), 而买入可以用一个状态(状态一)表示?
 *      因为有个冷冻期状态表示卖出的情况, 而且为了满足状态一中的今天买入情况以及状态四中的情况, 所以要拆分出来卖出两个状态
 *
 *  2. 确定递推公式:
 *     dp[i][0] = Math.max(-prices[i] + Math.max(dp[i-1][3], dp[i-1][1]), dp[i-1][0])
 *      dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3])
 *    dp[i][2] = prices[i] + dp[i-1][0]
 *     dp[i][3] = dp[i-1][2]
 * 3. 初始化
 *    dp[i][0] = -prices[0]
 *     dp[i][1] = 0
 *     dp[i][2] = 0
 *     dp[i][3] = 0
 * 4. 确定遍历顺序
 *    从前往后遍历
 *
 *  5. 返回最大值时 需要返回状态二、状态三、状态四其中的最大值
 *  时间: O(n)
 *  空间: O(n)
 */
public int maxProfit(int[] prices) 
   int[][] dp = new int[prices.length][4];

   dp[0][0] = -prices[0];

   for (int i = 1; i < prices.length; i++) 
      dp[i][0] = Math.max(-prices[i] + Math.max(dp[i-1][3], dp[i-1][1]), dp[i-1][0]);
      dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
      dp[i][2] = prices[i] + dp[i-1][0];
      dp[i][以上是关于LeetCode ---- 买卖股票系列问题思路与题解的主要内容,如果未能解决你的问题,请参考以下文章

菜鸟系列 Golang 实战 Leetcode —— 买卖股票的最佳时机系列(121. 买卖股票的最佳时机买卖股票的最佳时机 II

掌握这个方法,LeetCode 上的「股票买卖问题」就能为所欲为

贪心算法 | leetcode 买卖股票系列

Leetcode系列 买卖股票的最佳时机

LeetCode买卖股票之一:基本套路(122)

LeetCode买卖股票之一:基本套路(122)