1. 程式人生 > 實用技巧 >猜數字大小

猜數字大小

題目:

我們正在玩一個猜數遊戲,遊戲規則如下:

我從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;
    }
}