力扣之買賣股票的最佳時機

水冗水孚發表於2022-07-06

題目描述

給定一個陣列 prices ,它的第 i 個元素 prices[i] 表示一支給定股票第 i 天的價格。

你只能選擇 某一天 買入這隻股票,並選擇在 未來的某一個不同的日子 賣出該股票。設計一個演算法來計算你所能獲取的最大利潤。

返回你可以從這筆交易中獲取的最大利潤。如果你不能獲取任何利潤,返回 0

示例 1:

輸入:[7,1,5,3,6,4]
輸出:5
解釋:在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
     注意利潤不能是 7-1 = 6, 因為賣出價格需要大於買入價格;同時,你不能在買入前賣出股票。

示例 2:

輸入:prices = [7,6,4,3,1]
輸出:0
解釋:在這種情況下, 沒有交易完成, 所以最大利潤為 0。
力扣原題目地址:https://leetcode.cn/problems/...

思路分析

關於這道題目,可以兩次迴圈暴力求結果,不過不太好,此處不說暴力求解結果的程式碼方式(附在最後)

我們一般遇到一個複雜問題的時候,可以把複雜問題進行簡化拆解,在解決簡化拆解問題的時候,尋找規律、發現規律,最終在複雜問題上使用我們之前發現的簡化問題相似的規律,從而解決之

比如老闆上來就讓我們製造一個原子彈,啊,不會啊。沒關係我們先研究一下,如何製造一個手榴彈。反正都是蛋嘛,都是爆炸用的,總是有相通之處,手榴彈製造出來了,原子彈也就快了。這個比喻不太恰當,反正是那個意思,大家自行體會哈 ^_^

閱讀題目以後我們大概知道需求如下:

  • 要想獲得最高收益,就必須在低位買進、高位賣出

    • 即:找到陣列中的最大值,最小值,最大減最小就是最高收益
  • 股票是,先買後賣。所以買股票的時間靠前、賣股票的時間靠後

    • 即:最小值的位置索引要小於最大值的位置索引
  • 問題是,我們暫時不知道那天買進股票最低,那天最高???

乾脆,我們就假設,頭一天股票最低,3塊錢就能買,後面股票漲漲跌跌,但是,始終沒有低於3塊的。所以頭一天買進最划算。我們以5天為週期,刷卡付款頭一天買了。於是就模擬定義一個陣列:let arr = [3,8,6,12,9],接下來,我們把問題轉化成:

已知第一天買的股價最低,後面的每一天都比第一天股價高,求那一天賣掉最多能賺多少錢

複雜問題拆解之簡化

於是我們根據需求,可以寫出下面的程式碼:

let arr = [3, 8, 6, 12, 9]
function maxProfit(prices) {
    // 1. 假設初始收益為0,後面每一天的價格減去第一天的買入價格就是收益
    let maxProfitValue = 0
    for (let i = 1; i < prices.length; i++) {
        // 2. 只要收益大的(收益大的記在本子上,收益小的劃掉不要)
        maxProfitValue = Math.max(maxProfitValue, prices[i] - 3)
    }
    // 3. 最終得到一個最大收益,並返回
    return maxProfitValue;
}
console.log( maxProfit(arr) ); // 9

上述程式碼 maxProfitValue = Math.max(maxProfitValue, prices[i] - 3)我們可以換一下寫法,因為3是第一天的價格,所以換成arr[0] 即:

複雜問題拆解之簡單變換

let arr = [3, 8, 6, 12, 9]
function maxProfit(prices) {
    let min = prices[0] // 變換之~我們已知最低點股價是第一項,故定義變數表示
    // 1. 假設初始收益為0,後面每一天的價格減去第一天的買入價格就是收益
    let maxProfitValue = 0
    for (let i = 1; i < prices.length; i++) {
        // 2. 只要收益大的(收益大的記在本子上,收益小的劃掉不要)
        maxProfitValue = Math.max(maxProfitValue, prices[i] - min) // 變換之~最低價是min,這裡賣出高價-買入最低價也是用min變數了
    }
    // 3. 最終得到一個最大收益,並返回
    return maxProfitValue;
}
console.log( maxProfit(arr) ); // 9

看到這裡,有的朋友說了,假如第一天、陣列中的第一項不是最小的呢?假如第二項有可能比第一項小呢?沒關係,我們們對比一下就行了,誰小用誰。反正有迴圈,可以拿到每一項的值,對比就是嘍。虛擬碼如下:

let min = arr[0] // 假設第一項最小
min = Math.min(min,arr[i]) // 第二項和當下最小值做對比,誰小就保留誰
Math.min(5,2,8,3) // Math.min求一組數中的最小值 左側程式碼執行得到結果2
於是上述程式碼又可以做一個變換

複雜問題之變換解決之

function maxProfit(prices) {
    let min = prices[0] // 1. 假設最低股價是第一項,若後續有更低的股價,就用後續更低的
    let maxProfitValue = 0 // 2. 假設初始收益為0,後面每一天的價格減去買入價格就是收益
    for (let i = 1; i < prices.length; i++) {
        min = Math.min(min, prices[i]) // 3. 變換解決之,使用Math.min對比一下,看那一天的股價最低,就用那個
        // 4. 只要收益大的(收益大的記在本子上,收益小的劃掉不要)
        maxProfitValue = Math.max(maxProfitValue, prices[i] - min) // 5. 已知最低價是min,那麼收益就等於賣出價-最低價
    }
    return maxProfitValue; // 6. 最終得到一個最大收益,並返回
}

好了,看到這裡,力扣演算法題也就解決啦。所以思路很重要:複雜的問題簡單化,在簡單化處理的過程中不斷尋找規律,從而向複雜化過渡,最終通過尋得的規律解決複雜化的問題

附:雙層迴圈暴力解法

function maxProfit(prices) {
    let arr = [0] // 指定預設收益為0
    for (let i = 0; i < prices.length; i++) {
        for (let j = i + 1; j < prices.length; j++) {
            arr.push(prices[j] - prices[i]) // 把所有計算出的利潤都追加到一個陣列中
        }
    }
    return Math.max(...arr) // 然後求出這個陣列中的最大值即為最大收益
}

相關文章