package BDyNamicProgramming;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/4/24 0024 15:30
*/
public class Problem375 {
/**
*
* @param n
* @return
* 采用动态规划:
*
* dp[i][j]代表在区间[i,j]猜对所有需要的前
* 如果i==j 此时只要一次就能好成功 不需要找钱
* 否则,可能在[i,x-1]或者[x+1,j]猜对,
* 但在x出猜错,x在[i.j](需要穷举x的位置,获得至少的钱)
*
* 动态规划与二分的区别在哪里?
* 使用动态规划的好处在于我可以穷举所有的情况,对于这个题来说,
* 就是指动态规划的方法可以把每一个数字都作为分隔点,而二分只能把中间的点当作分割点
*
* 动态规划需要使用内存储存计算过的结果,在这里我使用一个二维数组dp[n+1][n+1]
*
* 对于动态规划来说:需要明白dp[i][j]的含义,所以接下来
* dp[i][j]是说依次从i到j的数字作为分割点(猜的数),必定赢得游戏所用钱的最小值
* /**
dp[i][j]表示从[i,j]中猜出正确数字所需要的最少花费金额.(dp[i][i] = 0)
假设在范围[i,j]中选择x, 则选择x的最少花费金额为: max(dp[i][x-1], dp[x+1][j]) + x
用max的原因是我们要计算最坏反馈情况下的最少花费金额(选了x之后, 正确数字落在花费更高的那侧)
初始化为(n+2)*(n+2)数组的原因: 处理边界情况更加容易,
例如对于求解dp[1][n]时x如果等于1, 需要考虑dp[0][1](0不可能出现, dp[0][n]为0)
而当x等于n时, 需要考虑dp[n+1][n+1](n+1也不可能出现, dp[n+1][n+1]为0)
如何写出相应的代码更新dp矩阵, 递推式dp[i][j] = max(max(dp[i][x-1], dp[x+1][j]) + x), x~[i:j],
可以画出矩阵图协助理解, 可以发现
dp[i][x-1]始终在dp[i][j]的左部, dp[x+1][j]始终在dp[i][j]的下部,
所以更新dp矩阵时i的次序应当遵循bottom到top的规则, j则相反, 由于
i肯定小
*
*
*/
public int getMoneyAmount(int n) {
int[][] dp = new int[n+2][n+2];
for(int i=n;i>=1;i--){
//猜测i到j
for(int j=i;j<=n;j++){
//上面这两层次区间用于穷举区间[i,j]
if(i==j){
//在区间[i,i]只需要猜一次就能猜对,所以不需要前
dp[i][j]=0;
}else{
//猜测i到j的所有可能性
dp[i][j]=Integer.MAX_VALUE;
//穷举猜错的位置
//max(dp[i][x-1],dp[x+1][j])表示取在x的左边猜对,x右边猜对最多的前
//最后需要加上x,本次猜错的前,就是本次在x猜错,但是在[i][x-1]或者[x+1][j]猜对所需要的前
//x猜测的钱数在 i与j之间
for(int x=i;x<=j;x++){//穷举所有x可能的位置(猜的位置)
dp[i][j]=Math.min(dp[i][j],Math.max(dp[i][x-1],dp[x+1][j])+x);
}
}
}
}
return dp[1][n];
}
}