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.
给一个数字n,对方从1~n中选一个数字,猜对方手中的数字,猜错的话赔偿所猜数字量的money,问保证能猜中所需的money。
思路:
保证能猜中说明要cover最坏的case,刚开始想到binary search,往较大的方向走,但是对[1,2]这种情形并不使用,因为这种情况猜1即可保证能赢,而往大方向走的binary search会是2。
所以要找出最坏情况,并在最坏的情况中选择较小的,即游戏理论中的min-max算法。
定义DP数组,dp[i][j]表示从i到j保证能赢的money
(1) 当i和j相同时,不用猜,所需money为0
(2) i和j相邻时,即i+1==j时,猜较小的那个cost较小,因为猜错了就知道选较大的那个,所需money为i
(3) i和j不相邻时,采取分段的方式把这一段拆分成较小的段,比如1,2,3,4
,i=1,j=4
依次取k=2,3把它分成两段
因为要取最坏情况的局部最大值,最后再从最坏的情况选最小的
localMax = max(dp[i][k-1], dp[k+1][j]) + k
globalMin = min(globalMin, localMax)
dp[i][j] = globalMin
*注意k要从右往左取值, 这样可以使localMax先取较大值,防止globalMin陷入局部最小值
最后返回dp[1][n]
public int getMoneyAmount(int n) {
if(n <= 1) {
return 0;
}
//dp[i][j]表示i~j区间保证赢需要的money
//只有一个数时不用猜,即dp[i][i] = 0
//只有两个数时猜较小的可保证赢,即j+1==i时,dp[j][i]=j
int[][] dp = new int[n+1][n+1];
//可能选到的数
for (int i = 2; i <= n; i++) {
//从相邻的数字开始逐渐扩大区间
//因为是对称矩阵,只需要取下三角
for (int j = i-1; j > 0; j--) {
//每个区间都有一个保证赢的money,因此每个区间都要初始化
//游戏中的min-max方法,每个小区间取max,整体大区间取所有小区间的min
int globalMin = Integer.MAX_VALUE;
for (int k = j+1; k < i; k++) {
int localMax = Math.max(dp[j][k-1], dp[k+1][i]) + k;
globalMin = Math.min(globalMin, localMax);
}
//只有两个数时猜较小的可保证赢,即j+1==i时,dp[j][i]=j
dp[j][i] = (j+1 == i) ? j : globalMin;
}
}
return dp[1][n];
}