Leetcode 題解系列 -- 股票的最大利潤(動態規劃)

安歌發表於2022-02-03

本專題旨在分享刷Leecode過程發現的一些思路有趣或者有價值的題目。【當然是基於js進行解答】。

動態規劃一樣是leetcode 中等難度習題的重點型別之一,同時可能也是面試熱點之一,所以重要性不言而喻。

image.png

題目相關

  • 原題地址: https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/
  • 題目描述:

    示例1:
    輸入: [7,1,5,3,6,4]
    輸出: 5
    解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。 (注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格。)
    - 示例2:
    輸入: [7,6,4,3,1]
    輸出: 0
    解釋: 在這種情況下, 沒有交易完成, 所以最大利潤為 0。
    - 限制:
  • <= 陣列長度 <= 10^5

思路解析

暴力破解

首先有一部分同學喜歡上來就來一波暴力破解,直接計算出所有可能的組合情況:

梭哈老頭原圖.png

找出給定陣列中兩個數字之間的最大差值(即,最大利潤)。此外,第二個數字(賣出價格)必須大於第一個數字(買入價格), 那麼所有的組合情況就是n*(n-1)/2,複雜度是O(n^2),毫無疑問,在題目給定的0 <= 陣列長度 <= 10^5下,妥妥超時; 而且面試如果暴力破解的話,估計也要被暴力pass了。

動態規劃

(這個名字一聽就很科學!)

其實早期詳細寫過動態規劃的基礎思路https://segmentfault.com/a/11...
建議不熟悉的同學可以先去看看這一篇。

而所謂動態規劃,核心就是就是把多階段過程,轉化為一系列單階段問題;把問題不斷拆解為子問題去求解

如果看過基礎篇的同學應該知道,這裡說的拆解,其實更直白一些就是找到第i個子問題與前i個子問題的關係。

帶入本道題其實就是 把求解n天裡買賣股票最高利潤的問題,先轉化為先求解第n天賣出股票時的最高利潤,然後從中找出最大值即可

image.png

以輸入 [7,1,5,3,6,4] 為例:

  1. 第1天賣出的話,最高利潤是0,等於是無交易;
  2. 第2天賣出的話,最高利潤是0,因為第二天價格是1,高出第1天,也不會交易;
  3. 第3天賣出的話,的最高利潤是4,也就是第二天買入,第三天賣出;
  4. 以此類推...

來觀察計算求解第i天賣出股票時,所能得到的最高利潤的核心,只要已知目前為止的歷史最低價,那麼前i天的最高利潤其實就是 用第i天的價格減去這個歷史最低價即可,那麼思路不就來了?

image.png

var maxProfit = function(prices) {
  const profit = []; // profit[i] 表示第i天賣出時的最大利潤 
  let historyMinPrice = Infinity;

  for(let i = 0; i <= prices.length; i++) {
      // 保持更新迄今為止的歷史最低價
      if(prices[i] < historyMinPrice) {
        historyMinPrice = prices[i];
      }
      profit[i] = prices[i] - historyMinPrice;
  }
  // 未完待續
  // ... 
};

那麼,有了這個price陣列之後, 只要獲取其中的最大值,其實也就是題目所要求的輸出了。 這個想必難不倒大家,可以直接迴圈比對一次獲得,但是其實思考下,並沒必要再進行一次迴圈,因為在原來的迴圈過程,我們其實就可以沿用歷史最低價的思路, 另外維持一個目前為止的最大利潤變數。 也就是

var maxProfit = function(prices) {
  const profit = []; // profit[i] 表示第i天賣出時的最大利潤 
  let historyMinPrice = Infinity;
  let maxProfit = 0;

  for(let i = 0; i <= prices.length; i++) {
      // 保持更新迄今為止的歷史最低價
      if(prices[i] < historyMinPrice) {
        historyMinPrice = prices[i];
      }
      profit[i] = prices[i] - historyMinPrice;
      if(profit[i] > maxProfit) {
        maxProfit = profit[i];
      }
  }
   return maxProfit;
};

image.png

還可以更優嗎

現在我們已經解出了這道題,那麼就到此為止了嗎? 回頭看看我們的程式碼,會發現是否有必要維持profit陣列的存在呢? 它的意義僅用於更新maxProfit而已 那麼是不是...

大膽一點! 直接把它移除掉!!

var maxProfit = function(prices) {
  // 幹掉profit[i] 
  let historyMinPrice = Infinity;
  let maxProfit = 0;

  for(let i = 0; i <= prices.length; i++) {
      // 保持更新迄今為止的歷史最低價
      if(prices[i] < historyMinPrice) {
        historyMinPrice = prices[i];
      }

      if(prices[i] - historyMinPrice > maxProfit) {
        maxProfit = prices[i] - historyMinPrice;
      }
  }
   return maxProfit;
};

到這裡是不是成就感更深了一層!

image.png

那麼本題的解答也就到此結束了,下期再見!

image.png

相關文章