package com.freetymekiyan.algorithms.level.medium;
/**
* We are playing the Guess Game. The game is as follows:
* <p>
* I pick a number from 1 to n. You have to guess which number I picked.
* <p>
* Every time you guess wrong, I'll tell you whether the number I picked is higher or lower.
* <p>
* 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.
* <p>
* Example:
* <p>
* n = 10, I pick 8.
* <p>
* 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.
* <p>
* Game over. 8 is the number I picked.
* <p>
* 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.
* <p>
* Hint:
* <p>
* 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 the first scenario.
* <p>
* Take a small example (n = 3). What do you end up paying in the worst case?
* <p>
* Check out this article if you're still stuck.
* <p>
* The purely recursive implementation of minimax would be worthless for even a small n. You MUST use dynamic
* programming.
* <p>
* 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?
* <p>
* Tags: Dynamic Programming
* Similar Problems: (M) Flip Game II, (E) Guess Number Higher or Lower
*/
public class GuessNumberHigherOrLower2 {
/**
* DP, top-down.
* Minimize the maximum of each possible move.
* The maximum here means the move will always lead to the worst branch.
* For each move, you can pick randomly from 1 to n, as i.
* You pay i dollars, and then pick another time from the rest of the two ranges:
* 1) From 1 to i - 1 2) From i + 1 to end .
* Recurrence relation:
* The most money to pay for this move = The money you pay for the current move + The maximum of the most money you
* pay for left part and right part.
* The minimum of the maximums is the answer.
*/
public int getMoneyAmount(int n) {
int[][] dp = new int[n + 1][n + 1]; // The amount of money from start to end.
return helper(dp, 1, n);
}
private int helper(int[][] dp, int start, int end) {
if (start >= end) { // When start == end, the number is found
return 0;
}
if (dp[start][end] != 0) { // Already calculated
return dp[start][end];
}
int res = Integer.MAX_VALUE; // Minimax: minimum of maximums
for (int i = start; i <= end; i++) {
int tmp = i + Math.max(helper(dp, start, i - 1), helper(dp, i + 1, end));
res = Math.min(res, tmp);
}
dp[start][end] = res; // Save in matrix
return res;
}
/**
* DP, bottom-up.
* Build result of i from 1 to n.
* For each number, get the global min of j from i - 1 to 1.
* To get the global min, calculate the local max of k from j + 1 to i.
* Finally, return dp[1][n] as the result.
*/
public int getMoneyAmountB(int n) {
int[][] dp = new int[n + 1][n + 1];
for (int j = 2; j <= n; j++) {
for (int i = j - 1; i > 0; i--) {
int globalMin = Integer.MAX_VALUE;
for (int k = i + 1; k < j; k++) {
int localMax = k + Math.max(dp[i][k - 1], dp[k + 1][j]);
globalMin = Math.min(globalMin, localMax);
}
dp[i][j] = i + 1 == j ? i : globalMin; // If i + 1 == j means there are only two numbers to choose
}
}
return dp[1][n];
}
}