動態規劃——C++做雞蛋掉落,簡單理解的但超出時間限制的dp,及優化後的二分dp

題目

N層樓,K個雞蛋,找樓層 F ,滿足高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破,問找到這個F需要測試的最小雞蛋數目

思路

dp的思路:找狀態,做選擇(max or min),窮舉選擇,思考base case

本題狀態就是雞蛋數K和要判斷的樓層數N,隨着測試(狀態變化)雞蛋數會減少,樓層數會減少;
選擇就是選擇去哪一層(i)丟雞蛋, 丟了雞蛋後有兩個結果:雞蛋碎了,接下來要去i層往下測試了(k --> k-1, N—>i-1 );雞蛋沒碎,往i+1到N層去測試(k—>k, N—>N-i)
那狀態轉移方程就定了,用dp[雞蛋數][樓層數]或者dp(雞蛋數, 樓層數)表示狀態轉移,外加一個for循環遍歷所有的選擇,以此擇優更新結果
res = min(res, max(dp(k-1, i-1), dp(k, N-i))+1)

base case 體現在代碼上就是最開始做的一些判斷(我覺得), 當N= =0,不需要測試返回0, 當K==1,那就只能從第一層開始往上測試,最壞情況就只能是返回N

拿一個雞蛋去某一層樓扔,關於要選擇去哪一層扔,我們不知道,那就把樓層都試一遍,至於雞蛋碎沒碎,下一次怎麼選擇,就不用去操心,因爲有正確的狀態轉移,遞歸會算出每個選擇的代碼,我們取最優的就是最優的

代碼,加備忘錄(超出時間限制)

class Solution {
public:
    int dp(int K, int N, vector<vector<int>>& memo)
    {
        //base case
        if(K==1) return N;
        if(N==0) return 0;
        if(memo[K][N] != -1) return memo[K][N];
        int res = INT_MAX;
        for(int i = 1; i<N+1; i++)//選擇在第i層開始,範圍是i到N+1
        {//(碎了,沒碎)+1
            res = min(res, max(dp(K-1, i-1,memo), dp(K, N-i, memo))+1); //+1表示在第i層樓扔了一次   
        }
           memo[K][N] = res;
           return res;
    }
    int superEggDrop(int K, int N) {
        //要有一個備忘錄,是二維數組,注意數組的大小,
        vector<vector<int>> memo(K+1, vector<int>(N+1, -1));
        return dp(K, N, memo);
    }
};

優化扔雞蛋

隨着N的增大,不管算法策略有多好,測試次數總是呈現增加趨勢的,那麼兩個狀態選擇dp(K-1, i-1)dp(K, N-i)固定K和N,隨着i的增加,前者會單調遞增,後者會單調遞減,要求這兩個選擇的較大值再求這些較大值中的最小值(圖來自力扣官方解析),就是兩條線的交點。
在這裏插入圖片描述
那就用二分查找來優化線性搜索的複雜度,
由於與上面的線性查找樓層沒有本質區別,所有空間複雜度與時間複雜度打敗率都不高,但是沒有超過時間限制了,就先這樣吧==

優化代碼

class Solution {
public:
    int dp(int K, int N, vector<vector<int>>&memo)
    {
        if(K == 1) return N;
        if(N == 0) return 0;
        if(memo[K][N] != -1) return memo[K][N];

        int res = INT_MAX;
        int low = 1, high = N;
        int mid = 0;
        int broken = 0, not_broken = 0;
        while(low <= high)//二分找樓層
        {
            mid =(low+high)/2;
            broken = dp(K-1, mid-1, memo);//碎了, 需要測試的次數
            not_broken = dp(K, N- mid, memo);//沒碎情況需要的測試第二個參數始終表示還有多少層樓待測試
            //取兩種情況下的較大值們的最小值
            if(broken > not_broken)
            {
                high = mid-1;
                res = min(res, broken+1);
            }else{
                low = mid+1;
                res = min(res, not_broken+1);
            }
        }
        memo[K][N] = res;
        return res;
    }
    int superEggDrop(int K, int N) {
        vector<vector<int>> memo(K+1, vector<int>(N+1, -1));
        return dp(K, N, memo);
    }
};

放個官方解答, 沒有本質區別,就是參考一下map的備忘錄

官方解答(隨手一放

//官方解答,用map做備忘錄,將K和N放一起作爲key,也蠻不錯,但本質都是一樣的
class Solution {
    unordered_map<int, int> memo;//用map做備忘錄
    int dp(int K, int N) {
        if (memo.find(N * 100 + K) == memo.end()) {
            int ans;
            if (N == 0) ans = 0;
            else if (K == 1) ans = N;
            else {
                int lo = 1, hi = N;//就是用二分代替線性查找
                while (lo + 1 < hi) {
                    int x = (lo + hi) / 2;
                    int t1 = dp(K-1, x-1);
                    int t2 = dp(K, N-x);

                    if (t1 < t2) lo = x;
                    else if (t1 > t2) hi = x;
                    else lo = hi = x;
                }

                ans = 1 + min(max(dp(K-1, lo-1), dp(K, N-lo)),
                                   max(dp(K-1, hi-1), dp(K, N-hi)));
            }

            memo[N * 100 + K] = ans;
        }

        return memo[N * 100 + K];
    }
public:
    int superEggDrop(int K, int N) {
        return dp(K, N);
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章