題目
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);
}
};