LeetCode887雞蛋掉落——dp


題目

題目鏈接

你將獲得 K 個雞蛋,並可以使用一棟從 1 到 N  共有 N 層樓的建築。
每個蛋的功能都是一樣的,如果一個蛋碎了,你就不能再把它掉下去,如果沒有碎可以繼續使用。
你知道存在樓層 F ,滿足 0 <= F <= N 任何從高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破。
每次移動,你可以取一個雞蛋(如果你有完整的雞蛋)並把它從任一樓層 X 扔下(滿足 1 <= X <= N)。
你的目標是確切地知道 F 的值是多少。
無論 F 的初始值如何,請你確定 F 的值的最小移動次數是多少?

例如:

輸入:K = 1, N = 2
輸出:2
輸入:K = 2, N = 100
輸出:14
輸入:K = 3, N = 14
輸出:4
注意
  • 1 <= K <= 100
  • 1 <= N <= 10000

思路

算法一

(動態規划)$O(K\!N)$

  1. 狀態$f(i,j)$表示使用$i$個雞蛋,最大樓層為$j$時,所需要的最小移動次數。
  2. 顯然初始狀態$f(1,j)=j$。對於狀態$f(i,j)$,如果枚舉上一次測試為樓層$s$,則可以得到如下轉移$f(i,j) = min_{s=1}^j(max(f(i-1,s-1),f(i,j-s)) + 1)$,分別表示在第$s$層摔碎和沒摔碎。
  3. 如果直接枚舉$s$,則總的時間復雜度為$O(K\!N^2)$,無法通過。考慮$f(i-1,s-1)和f(i,j-s)$的大小關系,可以發現,前者隨着$s$單調遞增,后者單調遞減,且每次變化的值最多為1(可證明,略)。所以,如果存在${s}'$使得$f(i-1,s-1) = f(i,j-s)$,則此時${s}'$就是最優的;否則取兩者最相近的兩個$s$作比較,取最小值。
  4. 至此,$s$可以二分解決;總的時間復雜度:$O(K\!N\!logN)$。
  5. 但進一步可以發現,$s$會隨着$j$的增加而增加,即最優決策點${s}'$是隨着$j$單調遞增的。因為對於固定的$s$,$f(i,j-s)$會隨着$j$而增加,這就會造成3中的最優決策點也會向后移動。所以,我們只需在每次移動$j$后,繼續從上次的${s}'$向后尋找最優決策點即可。
  6. 最終答案是$f(K,N)$

注意:在雞蛋沒摔碎時,我們還能用這$i$個雞蛋在在上面的$j-s$層確定$F$,這里的實驗與在第$1~(j-w)$所需的次數是一樣的,因為它們的實驗方法和步驟都是相同的,只不過這$(j-w)$層在上面罷了。

時間復雜度

  • 狀態數為$O(K\!N)$,對於每個$i$,尋找最優決策的均攤時間為$O(N)$,故總時間復雜度為$O(K\!N)$

C++代碼

 1 class Solution {
 2 public:
 3     int superEggDrop(int K, int N) {
 4         int m = K, n = N;
 5         const int maxn = 100 + 10;
 6         const int maxm = 10000 + 10;
 7         int d[maxn][maxm];        //dp[i][j]表示有i顆鷹蛋在j層樓的最少次數
 8         if (m >= ceil(log(n + 1) * 1.0) / log(2.0))
 9             return (int)ceil(log((n + 1) * 1.0) / log(2.0));
10         else
11         {
12             memset(d, 0, sizeof(d));
13             for (int i = 1; i <= n; i++)  d[1][i] = i;
14             for (int i = 2; i <= m; i++)
15             {
16                 int s = 1;
17                 for (int j = 1; j <= n; j++)
18                 {
19                     d[i][j] = d[i][j - 1] + 1;        //
20 
21                     while (s < j && d[i - 1][s] < d[i][j - s - 1])  s++;
22 
23                     d[i][j] = min(d[i][j], max(d[i - 1][s - 1], d[i][j - s]) + 1);
24                     if (s < j)  d[i][j] = min(d[i][j], max(d[i - 1][s], d[i][j - s - 1]) + 1);
25                 }
26             }
27             return d[m][n];
28         }
29     }
30 };

算法二

(動態規划)$O(KlogN)$

  1. 狀態$f(i,j)$表示進行$i$次移動,有$j$個雞蛋,最多可以檢查的樓層高度是多少。
  2. 初始狀態是$f(1,0)=0$,$f(1,j),j \geq 1$。
  3. 先給出轉移方程,$f(i,j)= f(i-1,j-1)+f(i-1,j)+1$。假設$n_1=f(i-1,j-1),n_2=f(i-1,j)$,我們在第$i$次移動時測試第$n_1+1$層。
  4. 如果測試時雞蛋碎掉了,則我們可以通過$i-1$次移動和$j-1$個雞蛋來找到最高不會碎掉的樓層,因為樓層不會超過$n_1$了;如果雞蛋沒有碎掉,則在此基礎上,我們可以使用$i-1$次移動和$j$個雞蛋,在繼續向上檢查$n_2$層,故答案在$\left [ 0, n_1 + n_2 + 1 \right ]$范圍內,都可以通過$i$次移動、$j$個雞蛋來找到。
  5. 返回最小的$m$滿足,$f(m,K) \geq N$。
  6. 這里第一維可以省略,更新時只需要倒序更新即可。

時間復雜度

  • 最多進行$logN$輪更新,每輪更新需要$O(K)$的時間,故時間復雜度$O(K\!logN)$。

C++代碼

 1 class Solution {
 2 public:
 3     int superEggDrop(int K, int N) {
 4         vector<int> f(K + 1, 1);
 5         f[0] = 0;
 6         int m = 1;
 7         while (f[K] < N) {
 8             for (int i = K; i >= 1; i--)
 9                 f[i] = f[i] + f[i - 1] + 1;
10             m++;
11         }
12 
13         return m;
14     }
15 };

 

在URAL OJ上也有這題,只是數據范圍有所不同:Chernobyl’ Eagle on a Roof

參考鏈接:

1、https://www.acwing.com/solution/leetcode/content/579/

2、朱晨光:《優化,再優化!——從《鷹蛋》一題淺析對動態規划算法的優化

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM