375. 猜数字大小 II

考点

  • 区间DP

思路

一、问题建模

给定一个整数区间 \([1,n]\),其中隐藏着一个未知整数。 算法每次可以选择猜测一个整数 \(x\),并立即支付代价 \(x\)。随后:

  • 若真实值小于 \(x\),问题缩小为区间 \([1, x-1]\)
  • 若真实值大于 \(x\),问题缩小为区间 \([x+1, n]\)
  • 若猜中,则过程结束。

目标是在任意真实值的情况下,都能够保证猜中,并且使得所需支付金额的最大值最小

该问题本质上是一个 极小化最坏代价(Minimax)问题,并且具有明显的区间最优子结构,适合使用区间动态规划求解。


二、状态定义

定义二维动态规划数组: \[ f[l][r] = \text{当真实答案只可能位于区间 } [l,r] \text{ 内时,保证猜中所需的最小金额} \]

边界条件

  • \(l \ge r\) 时,区间中至多只有一个数字,不需要支付任何费用即可确定答案:

\[ f[l][r] = 0 \]


三、决策过程分析

设当前需要处理的区间为 \([l,r]\),选择第一次猜测的数字为 \(x\),其中: \[ l \le x \le r \]

代价拆分

  • 本次猜测立即支付代价:\(x\)
  • 若真实值小于 \(x\),进入左子区间 \([l, x-1]\),额外代价为 \(f[l][x-1]\)
  • 若真实值大于 \(x\),进入右子区间 \([x+1, r]\),额外代价为 \(f[x+1][r]\)

由于需要保证在最坏情况下仍然有足够的金额,因此需要取左右子区间代价的最大值: \[ \text{WorstCost}(x) = x + \max\bigl(f[l][x-1], f[x+1][r]\bigr) \]


四、状态转移方程

为了使最坏情况下的总代价最小,应在所有可能的猜测 \(x\) 中取最优值: \[ \boxed{ f[l][r] = \min_{x \in [l,r]} \left( x + \max\bigl(f[l][x-1], f[x+1][r]\bigr) \right) } \] 这是该问题的核心状态转移方程。


五、计算顺序

由于状态 \(f[l][r]\) 依赖于更短的区间:

  • \(f[l][x-1]\)
  • \(f[x+1][r]\)

因此按区间长度从小到大进行动态规划:

  1. 枚举区间长度 \(\text{len} = 2 \sim n\)
  2. 根据区间长度确定左右端点
  3. 枚举区间内的所有猜测点 \(x\)

最终答案为: \[ f[1][n] \]


六、空区间的处理

\(x = l\)\(x = r\) 时,会出现空区间:

  • \([l, x-1]\)\([x+1, r]\)

这些区间在语义上表示「无需再猜」,其代价应当为 0。 实现中必须显式处理这一情况,避免错误使用初始化的无穷大值。


七、AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Solution {
public:
static const int maxn = 2e2 + 5;
static const int inf = 0x3f3f3f3f;

int getMoneyAmount(int n) {
// f[l][r] 表示当真实答案位于区间 [l, r] 内时,
// 为保证一定猜中所需要的最小金额
int f[maxn][maxn];
memset(f, 0x3f, sizeof(f));

// 边界情况:区间中只有一个数(或为空),不需要花钱
for (int i = 1; i <= n; ++i)
f[i][i] = 0;

int i, l, r, best;

// 枚举区间长度
for (int len = 2; len <= n; ++len) {
// 枚举区间右端点
for (int j = len; j <= n; ++j) {
// 根据长度确定左端点
i = j - len + 1;
best = inf;

// 枚举当前区间内第一次猜测的数字 x
for (int x = i; x <= j; ++x) {
// 左子区间的最坏代价(若为空区间则为 0)
l = (x - 1 < i) ? 0 : f[i][x - 1];
// 右子区间的最坏代价(若为空区间则为 0)
r = (x + 1 > j) ? 0 : f[x + 1][j];

// 选择 x 作为第一次猜测时的最坏总代价
best = min(best, x + max(l, r));
}

// 得到区间 [i, j] 的最优解
f[i][j] = best;
}
}

// 整个区间 [1, n] 的最小保证代价
return f[1][n];
}
};

八、总结

  • 本题通过区间 DP 对「最坏情况下的最优决策」进行建模;
  • 状态设计的关键在于:
    • 使用区间 \([l,r]\) 表示信息状态;
    • 将猜测数字本身作为代价;
    • 通过 min + max 实现 Minimax 思想;
  • 正确处理空区间是实现正确性的关键细节。