刷題找工作《買賣股票問題》一文通解

NO.121 買賣股票的最佳時機 簡單 、NO.122 買賣股票的最佳時機II 簡單 、NO.123 買賣股票的最佳時機III 困難 、NO.188 買賣股票的最佳時機IV 困難

NO.121 買賣股票的最佳時機 簡單

3zzoW9.png

思路一:暴力法 沒什麼好說的,雙重循環計算所有元素兩兩組合相減的結果,取最大。

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

時間複雜度:O(n^2)

思路二:優化暴力法到一次遍歷 買賣股票從第二天開始我們每天都會"後悔":後悔沒有在之前的最低點進行買入,只有這樣我們的收益纔會最大化。

由此可見,我們想要當天利益最大化,只需要在過去的某個最低點買入股票就好。所以我們只需要記錄曾經出現過的最低點就好。

public int maxProfit(int[] prices) {
    int minPoint=Integer.MAX_VALUE,maxProfit=0;
    for (int i = 0; i < prices.length; i++) {
        //記錄曾出現過最低點
        minPoint=Math.min(prices[i],minPoint);
        //當日-曾經的最低
        maxProfit=Math.max(maxProfit,prices[i]-minPoint);
    }
    return maxProfit;
}

時間複雜度:O(n)

從NO.121題不難看出:買股票的最佳時機是曾經!股市有風險,入股需謹慎!(狗頭)

單純的解答本題是比較簡單的,但是買賣股票可以算作是一個系列的經典問題,在leetcode上就有本題一系列的變種問題:買賣股票的最佳時機、買賣股票的最佳時機II、買賣股票的最佳時機III、買賣股票的最佳時機IV、買賣股票的最佳時機含冷凍期、買賣股票的最佳時機含手續費。

雖然這些題有難有易,但是既然是一類問題,就有這一些通用的方法。

這六個問題都是由第四個問題簡化變種而來的,第四題相較於本題多了一個參數k,限制只能進行k次交易;第一題也就是本題是隻進行一次交易,相當於 k = 1;第二題是不限交易次數,相當於 k = +infinity(正無窮);第三題是隻進行 2 次交易,相當於 k = 2;剩下兩道也是不限次數,但是加了交易「冷凍期」和「手續費」的額外條件,其實就是第二題的變種。

NO.122 買賣股票的最佳時機II 簡單

在這裏插入圖片描述
JBe9iV.png

思路一:貪心算法 姊妹題NO.121,區別在於NO.121題只允許交易一次(一次買入、一次賣出),而本題沒有交易次數的限制。

所以想到貪心算法,因爲我們是"上帝視角"知道"未來股價",所以只要賺錢我們每天都進行交易。

所謂賺錢就是:“明天i+1股價”-“今天i股價”>0,這種情況今天就買入;否則就是賠錢或者不賺錢的情況,不進行交易。

public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0) return 0;
    int Profit = 0;
    for (int i = 0; i < prices.length - 1; i++) {
        //今天i買,明天i+1賺錢。就今天買入,明天賣。
        int sub = prices[i + 1] - prices[i];
        if (sub > 0) {
            Profit += sub;
        }
    }
    return Profit;
}

時間複雜度:O(n)

思路二:動態規劃 貪心算法已經很好地解決了這個問題,但是我們還是要帶入一下DP解法,爲後序的姊妹題NO.122買賣股票的最佳時機III、以及等等很多其他的XXX買賣股票問題都是基於本題進行變化的,無非是在本題的基礎上增加更多的限制條件。

dp[i][j]的含義:i表示第i天,j表示持股狀態(0表示不持股,1表示持股)。dp[i][j]整體就表示第i天持股/不持股時手裏的錢。

初始化:dp[0][0]=0,剛開始不賠不賺;dp[0][1]= -prices[0],只支出了。

狀態轉移:dp[i][0]=Max(dp[i-1][0],dp[i-1][1]+prices[i]),即第i天不持股時手裏錢最多爲MAX(i-1天不持股i天繼續不持股,i-1天持股+i天以當日價格賣掉股票);

同理dp[i][1]=Max(dp[i-1][0]-prices[i],dp[i-1][1]),即第i天持股時手裏錢最多爲MAX(i-1天不持股-i天以當日價格買入股票,i-1天持股i天繼續持股)。

public int maxProfit(int[] prices) {
    if (prices == null || prices.length < 2) return 0;
    //初始化
    int[][] dp = new int[prices.length][2];
    dp[0][0] = 0;
    dp[0][1] -= 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]);
        dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
    }
    //最後一天一定是不持股的
    return dp[prices.length - 1][0];
}

時間複雜度:O(n)

NO.123 買賣股票的最佳時機III 困難

JsFsEt.png

思路一:動態規劃 NO.121是隻能買賣一次股票,NO.122是任意買賣股票,而本題是隻能買賣股票兩次。

相較於前兩題的狀態,本題又增加了一個交易次數的狀態k。

dp[i][j][k]的含義:第i天的利潤,j表示持股狀態(0不持股,1持股),k交易次數(賣出次數0~2)。所以每天有六種狀態,思路就是從每天的六種狀態中找到利潤最大的。

初始化dp[0][0][0]=dp[0][0][1]=dp[0][0][2]=0,啥也沒幹;dp[0][1][0]=dp[0][1][1]=dp[0][1][2]=-prices[0],第一天持股只有支出。

狀態轉移dp[i][0][0]=dp[i-1][0][0],第i天不持股交易次數0,第i-1天一定未持股未交易,維持現狀;

dp[i][0][1]=MAX(dp[i-1][0][1],dp[i-1][1][0]+prices[i]),第i天不持股1次交易,維持第i-1天的狀態,或者第i-1天持股未交易第i天賣出。

dp[i][0][2]=MAX(dp[i-1][0][2],dp[i-1][1][1]+prices[i]),第i天不持股2次交易,維持第i-1天的狀態,或者第i-1天持股1次交易第i天賣出。

dp[i][1][0]=MAX(dp[i-1][1][0],dp[i-1][0][0]-prices[i]),第i天持股未交易,維持第i-1天的狀態,或者第i-1天不持股未交易第i天買入。

dp[i][1][1]=MAX(dp[i-1][1][1],dp[i-1][0][1]-prices[i]),第i天持股1次交易,維持第i-1天的狀態,或者第i-1天不持股1次交易第i天買入。

dp[i][1][2]=dp[i][1][2],第i天持股2次交易,已經到達交易限制,但是手中還持股,不需要考慮一定不能利潤最大化。

最終的結果一定是在不持股的三種狀態中進行比較。

public int maxProfit(int[] prices) {
    if (prices == null || prices.length < 2) return 0;
    //初始化
    int[][][] dp = new int[prices.length][2][3];
    dp[0][0][0] = dp[0][0][1] = dp[0][0][2] = 0;
    dp[0][1][0] = dp[0][1][1] = dp[0][1][2] = -prices[0];
    //狀態轉移
    for (int i = 1; i < prices.length; i++) {
        //六種狀態
        dp[i][0][0] = dp[i - 1][0][0];
        dp[i][0][1] = Math.max(dp[i - 1][0][1], dp[i - 1][1][0] + prices[i]);
        dp[i][0][2] = Math.max(dp[i - 1][0][2], dp[i - 1][1][1] + prices[i]);
        dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][0][0] - prices[i]);
        dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][0][1] - prices[i]);
        dp[i][1][2] = dp[i - 1][1][2];
    }
    //最大利潤一定是不持股的三種狀態之一。
    return Math.max(dp[prices.length - 1][0][2],
                    Math.max(dp[prices.length - 1][0][1], dp[prices.length - 1][0][0]));
}

時間複雜度:O(n) 空間O(n)雖然是三維數組,但2和3是常數。

NO.188 買賣股票的最佳時機IV 困難

YbtGLR.png

思路一:動態規劃 在上一題的基礎上兩次交易,變味了 k 次交易。但是需要考慮的狀態,仍然是三個,第 i 天、j 持股狀態、k 進行過的交易次數。

雖然 k 從上一題的常量 2 變爲了本題的變量,但是 dp 數組的含義不變。但是本題如果使用三維數組會因爲 k 值過大,導致內存超出。

所以採用兩個一維數組 dpi0[k+1] 和 dpi1[k+1] 表示第 i 天持股(1)或不持股(0)時,不同交易次數下的利潤。(上一題也可以採用這個方法,進行空間優化)

實現上,由於本題 k 不是上一題中的常量 2 ,無法手動寫死每種情況,所以採用一個 for 循環即可。

public int maxProfit(int k, int[] prices) {
    if (k<=0||prices.length<1)return 0;
    //最多隻能交易 prices.lenght/2 次
    if (k>prices.length/2) k = prices.length/2;
    //初始化
    int[] dpi0 = new int[k+1];
    int[] dpi1 = new int[k+1];
    for (int j = 1; j < k + 1; j++) {
        dpi1[j] = -prices[0];
    }
    //狀態轉移
    for (int i = 1; i < prices.length; i++) {
        for (int j = k; j >= 1; j--) {
            //第i天不持股 = Max(第i-1天不持股,第i-1天持股+第i天出售)
            dpi0[j] = Math.max(dpi0[j],dpi1[j]+prices[i]);
            //第i天持股 = Max(第i-1天持股,第i-1天不持股-第i天買入)
            dpi1[j] = Math.max(dpi1[j],dpi0[j-1]-prices[i]);
        }
    }
    return dpi0[k];
}

時間複雜度:O(n*k) n 共n天

空間複雜度:O(k)


本人菜鳥,有錯誤請告知,感激不盡!

更多題解和學習記錄博客:博客github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章