1. 程式人生 > 其它 >力扣714-買賣股票的最佳時機含手續費(動態規劃-Java詳細題解)

力扣714-買賣股票的最佳時機含手續費(動態規劃-Java詳細題解)

技術標籤:演算法刷題演算法java動態規劃leetcode

力扣714-買賣股票的最佳時機含手續費

一、原題題目

1.1 題目

​  給定一個整數陣列 prices,其中第 i 個元素代表了第 i 天的股票價格 ;非負整數 fee 代表了交易股票的手續費用。你可以無限次地完成交易,但是你每筆交易都需要付手續費。如果你已經購買了一個股票,在賣出它之前你就不能再繼續購買股票了。返回獲得利潤的最大值。
​  注意:這裡的一筆交易指買入持有並賣出股票的整個過程,每筆交易你只需要為支付一次手續費。

1.2 示例

  • 示例一
    輸入: 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.
  • 注意:
    • 0 < prices.length <= 50000.
    • 0 < prices[i] < 50000.
    • 0 <= fee < 50000.

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee

二、解題思路(動態規劃)

2.1 題目理解

  • 題前感慨(時間緊不用看,跳至【言歸正傳】部分) <br />
    ​  給定每日股票價格的陣列,每天可以選擇是否買入/賣出,持有時不能再次買入,每筆交易有固定的手續費,求可獲得的最大利潤。給定每日股票價格的陣列,每天可以選擇是否買入/賣出,持有時不能再次買入,每筆交易有固定的手續費,求可獲得的最大利潤。
    ​​  這是一道入門的動態規劃題目…當說出這句話的時候表示說話者已經對動態規劃有所掌握了,所以能很快的分析出它是一道動態規劃題,並且很容易就能想到什麼狀態變數,轉移方程的所以它是一道簡單的動態規劃題目。但對於很多演算法新手來說,難的是識別它是個動態規劃題。
    ​​  本文主要解決該演算法題,下面會結合本題簡單說到動態規劃,動態規劃的詳細講解自己也會全面的整理一份資料供自己總結複習,同時也分享出來給需要的網友參考~。
  • 言歸正傳
    ​ ​ 【1.識別動態規劃】 動態規劃所處理的問題是一個多階段決策問題,一般由初始狀態開始,通過對中間階段決策的選擇,達到結束狀態,此問題當中我們的初試狀態就是第0天開始,結束狀態就是第n天結束。中間的買入/賣出就是中間決策的選擇。
    ​​ 【2.確定狀態定義狀態變數】 知道是動態規劃題後就老老實實的確定狀態和定義狀態變數,如何正確合理的確定狀態也至關重要,例如這裡可以以每天交易結束後手裡是否有股票為狀態,兩種狀態:有股票,沒有股票。我們的目的是求最後的最大利潤的,所以定義狀態變數可以這樣定義:dp[i][0] 表示第 i 天交易結束後手裡沒有股票的最大利潤。dp[i][1] 表示第 i 天交易結束後手裡有股票的最大利潤。
    ​​ 【3.推導狀態轉移方程】 先考慮 dp[i][0] 的轉移方程,第 i 天結束,手裡沒有股票,那麼一定是今天沒有買入。可能的狀態有前一天可能是也沒有股票狀態 dp[i-1][0],或前一天結束時手裡有股票 dp[i-1][1]被我今天賣了。因此轉移方程為:
    d p [ i ] [ 0 ] = m a x { d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] − f e e } dp[i][0] = max\{dp[i-1][0],dp[i-1][1]+prices[i]-fee\} dp[i][0]=max{dp[i1][0],dp[i1][1]+prices[i]fee}
    ​​  在考慮 dp[i][1] 的轉移方程,第 i 天結束後,手裡有股票,那麼一定是今天沒有賣出。可能的狀態有前一天本來就是有股票狀態 dp[i-1][1],或者前一天結束時沒有股票dp[i-1][0],但我今天買入了。因此轉移方程為:
    d p [ i ] [ 1 ] = m a x { d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] } dp[i][1] = max\{dp[i-1][1],dp[i-1][0]-prices[i]\} dp[i][1]=max{dp[i1][1],dp[i1][0]prices[i]}
    ​​ 【4.確定邊界】 其實就是確定初始狀態,第0天交易結束時有 dp[0][0] = 0,dp[0][1] = -prices[0]。

2.2 詳細程式碼(Java)

public class Solution {
    public int maxProfit(int[] prices, int fee) {
        if (prices.length <= 1) return 0;
        int[][] dp = new int[prices.length][2];     // 狀態變數,儲存每天有或沒有股票的最大收益
        dp[0][0] = 0;           // 初始值,第0天沒股票即沒有買入,收益為0
        dp[0][1] = -prices[0];  // 初始值,第0天有股票即買入了,收益為-prices[0]
        for (int i = 1;i<prices.length;i++){        // 迴圈更新每天的狀態
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i] - fee);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i]);
        }
        return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]); // 返回最後一天兩種狀態的最大值
    }
}

2.3 演算法執行結果

演算法執行結果
​​  演算法效能不是很好,其他很多題友可能想出了更好的方法。這裡重點總結該題對動態規劃的運用,空間複雜度上可以稍微提升一下,因為在上述演算法中定義了一個二維陣列去求每一天的狀態,是沒有必要的,因為最後的輸出只需要最後一天的。我們可以定義兩個變數來儲存就好了。
public class Solution {
    public int maxProfit(int[] prices, int fee) {
        if (prices.length <= 1) return 0;
        int nothave = 0;            // 初始值,第0天沒股票即沒有買入,收益為0
        int have = -prices[0];      // 初始值,第0天有股票即買入了,收益為-prices[0]
        for (int i = 1;i<prices.length;i++){    // 迴圈更新狀態值
            int n = nothave,y = have;           // 下面的更新中都要用到之前的值,所以要臨時儲存一下
            nothave = Math.max(n,y + prices[i] - fee);
            have = Math.max(y,n - prices[i]);
        }
        return Math.max(nothave,have);          // 返回兩種狀態的最大值
    }
}
改進演算法執行結果
​​  居然效能也得到了很大的提升.......