猜數字大小
題目:
我們正在玩一個猜數遊戲,遊戲規則如下:
我從1到 n 之間選擇一個數字,你來猜我選了哪個數字。
每次你猜錯了,我都會告訴你,我選的數字比你的大了或者小了。
然而,當你猜了數字 x 並且猜錯了的時候,你需要支付金額為 x 的現金。直到你猜到我選的數字,你才算贏得了這個遊戲。
例如:
n = 10, 我選擇了8.
第一輪: 你猜我選擇的數字是5,我會告訴你,我的數字更大一些,然後你需要支付5塊。
第二輪: 你猜是7,我告訴你,我的數字更大一些,你支付7塊。
第三輪: 你猜是9,我告訴你,我的數字更小一些,你支付9塊。
遊戲結束。8 就是我選的數字。
你最終要支付 5 + 7 + 9 = 21 塊錢。
給定n ≥ 1,計算你至少需要擁有多少現金才能確保你能贏得這個遊戲。
解題
這道題要求的是最壞情況下的最好值。如果n=10,先猜7,再依據大小猜9或4,如果猜9再不對,那一定是8或10,花費7+9=16。如果是猜4不對再往下猜也不會超過16,所以如果是n=10,結果是16。因此不能使用二分法,二分法每次只能取中間值mid,這道題裡要取到範圍內每個值進行比較。
定義一個矩陣dp,讓其長寬各+2是為了邊界情況。
dp[i][j] 表示從 i 到 j 範圍內至少需要多少現金。
對於每一個 i 和 j 的範圍,依次對每個數取值,即 k=i~j ,
當猜k時,有兩種情況:小於k或大於k,即在i~k-1或k+1~j範圍內,
因此比較dp[i][k-1]+k 和 dp[k+1][j]+k 取更大者,再計算下一個k的情況。
將每個k值得到的值比較,最小值為dp[i][j]。
class Solution { public int getMoneyAmount(int n) { int[][] dp=new int[n+2][n+2]; for(int i=n;i>0;i--){ for(int j=i+1;j<=n;j++){ dp[i][j]=Integer.MAX_VALUE;for(int k=i;k<=j;k++){ dp[i][j]=Math.min(dp[i][j],Math.max(dp[i][k-1],dp[k+1][j])+k); } } } return dp[1][n]; } }
類似題目:
扔雞蛋
你將獲得K個雞蛋,並可以使用一棟從1到N共有 N層樓的建築。
每個蛋的功能都是一樣的,如果一個蛋碎了,你就不能再把它掉下去。
你知道存在樓層F ,滿足0 <= F <= N 任何從高於 F的樓層落下的雞蛋都會碎,從F樓層或比它低的樓層落下的雞蛋都不會破。
每次移動,你可以取一個雞蛋(如果你有完整的雞蛋)並把它從任一樓層X扔下(滿足1 <= X <= N)。
你的目標是確切地知道 F 的值是多少。
無論 F 的初始值如何,你確定 F 的值的最小移動次數是多少?
dp[k][m] 的含義是k個雞蛋 移動m次最多能夠確定多少樓層
dp[k][m] 最多能夠確定的樓層數為L
那麼我選定第一個扔的樓層之後,我要麼碎,要麼不碎
這就是把L分成3段
左邊是沒碎的那段 長度是dp[k][m - 1]
右邊是碎的那段 長度是dp[k-1][m - 1] 因為已經碎了一個了
中間是我選定扔的樓層 是1
所以遞推公式是
dp[k][m] = dp[k - 1][m - 1] + dp[k][m - 1] + 1
class Solution { public int superEggDrop(int K, int N) { int[][] dp = new int[K + 1][N + 1]; for (int m = 1; m <= N; m++) { dp[0][m] = 0; // zero egg for (int k = 1; k <= K; k++) { dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; if (dp[k][m] >= N) { return m; } } } return N; } }