1. 程式人生 > 實用技巧 >記憶化搜尋狀態重疊

記憶化搜尋狀態重疊

在做 LeetCode309. 最佳買賣股票時機含冷凍期時,寫完遞迴超時後,企圖用記憶化搜尋優化,但是發現原本正確的結果變成錯的了

Debug過程:

對於 [2,1,4] 這個用例,列出所有情況後發現最後一步的 not sell 和 buy 兩個狀態重疊了,導致記憶化錯誤

隨後改為自頂向下的dp通過

查閱資料發現:https://www.cnblogs.com/zcy0917/p/9316487.html


記憶化搜尋的適用範圍

根據記憶化搜尋的思想,它是解決重複計算,而不是重複生成,也就是說,這些搜尋必須是在搜尋擴充套件路徑的過程中分步計算的題目,也就是“搜尋答案與路徑相關”的題目,而不能是搜尋一個路徑之後才能進行計算的題目,必須要分步計算,並且搜尋過程中,一個搜尋結果必須可以建立在同類型問題的結果上,也就是類似於動態規劃解決的那種。

也就是說,他的問題表達,不是單純生成一個走步方案,而是生成一個走步方案的代價等,而且每走一步,在搜尋樹/圖中生成一個新狀態,都可以精確計算出到此為止的費用,也就是,可以分步計算,這樣才可以套用已經得到的答案.

使用條件

  • 對於一定狀態有唯一相同的解,不應對於一個狀態有多個解(解不相同);
  • 到達底層時可立即返回解,不應得出路徑後才能計算解;
  • 狀態數量和規模應能夠在有限資料結構中儲存。

TIP

  • 遵循無後效性原則;//某階段的狀態一旦確定,則此後過程的演變不再受此前各種狀態及決策的影響。
  • 侷限性:可十分簡潔的優化為遞迴,即動態規劃。
  • 往往對於dfs才會使用記憶化,因為bfs並不會重複搜尋到某一個狀態,而一旦搜尋到結果就是最優解,此時立即退出;

遞迴:

class Solution {
    int[] prices;

    public int iter(int idx, boolean have, boolean frozen, int profit){
        if (idx == prices.length - 1){
            if (have){
                profit += prices[prices.length - 1];
            }
            return profit;
        }

        if (frozen){

            
return iter(idx + 1, have, false, profit); } if (have){ int sell = iter(idx+1, false, true, profit + prices[idx]); int notsell = iter(idx+1, true, false, profit); return Math.max(sell, notsell); }else { int buy = iter(idx+1, true, false, profit - prices[idx]); int notbuy = iter(idx+1, false, false, profit); return Math.max(buy, notbuy); } } public int maxProfit(int[] prices){ if (prices == null || prices.length <=0){ return 0; } this.prices = prices; return iter(0, false, false, 0); } }

記憶化搜尋:

public class Solution {
    int[] prices;
    Integer[][][] dp;
    int len;

    public int iter(int idx, int have, int frozen, int profit){
        if (dp[idx][have][frozen] != null){
            return dp[idx][have][frozen];
        }

        if (idx == prices.length - 1){
            if (have == 1){
                profit += prices[prices.length - 1];
            }

            dp[idx][have][frozen] = profit;
            return profit;
        }


        int res;
        if (frozen == 1){
            res = iter(idx + 1, 0, 0, profit);
            dp[idx][have][frozen] = res;
            return res;
        }

        if (have == 1){
            int sell = iter(idx+1, 0, 1, profit + prices[idx]);
            int notsell = iter(idx+1, 1, 0, profit);
            res = Math.max(sell, notsell);
            
        }else {
            int buy = iter(idx+1, 1, 0, profit - prices[idx]);
            int notbuy = iter(idx+1, 0, 0, profit);
            res = Math.max(buy, notbuy);
        }
        dp[idx][have][frozen] = res;
        return res;
    }

    public int maxProfit(int[] prices){
        if (prices == null || prices.length <=0){
            return 0;
        }
        this.prices = prices;
        this.len = prices.length;
        dp = new Integer[len][2][2];
        return iter(0, 0, 0, 0);
    }
}

dp:

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <=0){
            return 0;
        }
        int len = prices.length;
        // 這天開始的時候 idx, 有無股票, 是否在冷凍期
        Integer[][][] dp = new Integer[len][2][2];

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

        // 不可能出現dp[i][1][1]

        for (int i = len - 2; i >= 0; --i) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 2; k++) {
                    if (k == 1){
                        if (j != 1){
                            // i 0 1
                            dp[i][j][k] = dp[i+1][0][0];
                        }
                    }
                    else {
                        if (j == 1){
                            // sell / not sell
                            dp[i][j][k] = Math.max(dp[i+1][0][1] + prices[i], dp[i+1][1][0]);
                        }else {
                            // buy / not buy
                            dp[i][j][k] = Math.max(dp[i+1][1][0] - prices[i], dp[i+1][0][0]);
                        }
                    }
                }
            }
        }

        return dp[0][0][0];
    }
}