We are playing the Guess Game. The game is as follows:
I pick a number from 1 to n. You have to guess which number I picked.
Every time you guess wrong, I'll tell you whether the number I picked is higher or lower.
However, when you guess a particular number x, and you guess wrong, you pay $x. You win the game when you guess the number I picked.
Example:
n = 10, I pick 8. First round: You guess 5, I tell you that it's higher. You pay $5. Second round: You guess 7, I tell you that it's higher. You pay $7. Third round: You guess 9, I tell you that it's lower. You pay $9. Game over. 8 is the number I picked. You end up paying $5 + $7 + $9 = $21.
Given a particular n ≥ 1, find out how much money you need to have to guarantee a win.
Hint:
- The best strategy to play the game is to minimize the maximum loss you could possibly face. Another strategy is to minimize the expected loss. Here, we are interested in thefirst scenario.
- Take a small example (n = 3). What do you end up paying in the worst case?
- Check out this article if you're still stuck.
- The purely recursive implementation of minimax would be worthless for even a small n. You MUST use dynamic programming.
- As a follow-up, how would you modify your code to solve the problem of minimizing the expected loss, instead of the worst-case loss?
Credits:
Special thanks to @agave and @StefanPochmann for adding this problem and creating all test cases.
此題是之前那道 Guess Number Higher or Lower 的拓展,難度增加了不少,根據題目中的提示,這道題需要用到 Minimax 極小化極大算法,關於這個算法可以參見這篇講解,並且題目中還說明了要用 DP 來做,需要建立一個二維的 dp 數組,其中 dp[i][j] 表示從數字i到j之間猜中任意一個數字最少需要花費的錢數,那么需要遍歷每一段區間 [j, i],維護一個全局最小值 global_min 變量,然后遍歷該區間中的每一個數字,計算局部最大值 local_max = k + max(dp[j][k - 1], dp[k + 1][i]),這個正好是將該區間在每一個位置都分為兩段,然后取當前位置的花費加上左右兩段中較大的花費之和為局部最大值,為啥要取兩者之間的較大值呢,因為要 cover 所有的情況,就得取最壞的情況。然后更新全局最小值,最后在更新 dp[j][i] 的時候看j和i是否是相鄰的,相鄰的話賦為j,否則賦為 global_min。這里為啥又要取較小值呢,因為 dp 數組是求的 [j, i] 范圍中的最低 cost,比如只有兩個數字1和2,那么肯定是猜1的 cost 低,是不有點暈,沒關系,博主繼續來繞你。如果只有一個數字,那么不用猜,cost 為0。如果有兩個數字,比如1和2,猜1,即使不對,cost 也比猜2要低。如果有三個數字 1,2,3,那么就先猜2,根據對方的反饋,就可以確定正確的數字,所以 cost 最低為2。如果有四個數字 1,2,3,4,那么情況就有點復雜了,策略是用k來遍歷所有的數字,然后再根據k分成的左右兩個區間,取其中的較大 cost 加上k。
當k為1時,左區間為空,所以 cost 為0,而右區間 2,3,4,根據之前的分析應該取3,所以整個 cost 就是 1+3=4。
當k為2時,左區間為1,cost 為0,右區間為 3,4,cost 為3,整個 cost 就是 2+3=5。
當k為3時,左區間為 1,2,cost 為1,右區間為4,cost 為0,整個 cost 就是 3+1=4。
當k為4時,左區間 1,2,3,cost 為2,右區間為空,cost 為0,整個 cost 就是 4+2=6。
綜上k的所有情況,此時應該取整體 cost 最小的,即4,為最后的答案,這就是極小化極大算法,參見代碼如下:
解法一:
class Solution { public: int getMoneyAmount(int n) { vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0)); for (int i = 2; i <= n; ++i) { for (int j = i - 1; j > 0; --j) { int global_min = INT_MAX; for (int k = j + 1; k < i; ++k) { int local_max = k + max(dp[j][k - 1], dp[k + 1][i]); global_min = min(global_min, local_max); } dp[j][i] = j + 1 == i ? j : global_min; } } return dp[1][n]; } };
下面這種是遞歸解法,建立了記憶數組 memo,減少了重復計算,提高了運行效率,核心思想跟上面的解法相同,參見代碼如下:
解法二:
class Solution { public: int getMoneyAmount(int n) { vector<vector<int>> memo(n + 1, vector<int>(n + 1, 0)); return helper(1, n, memo); } int helper(int start, int end, vector<vector<int>>& memo) { if (start >= end) return 0; if (memo[start][end] > 0) return memo[start][end]; int res = INT_MAX; for (int k = start; k <= end; ++k) { int t = k + max(helper(start, k - 1, memo), helper(k + 1, end, memo)); res = min(res, t); } return memo[start][end] = res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/375
類似題目:
參考資料:
https://leetcode.com/problems/guess-number-higher-or-lower-ii/
https://leetcode.com/problems/guess-number-higher-or-lower-ii/discuss/84787/Java-DP-solution