二分水位线 —— 最小差值平方和
二分水位线 —— 最小差值平方和
核心思想:当操作次数
k大到无法逐步模拟时,不去模拟每一次操作,而是二分出所有元素最终被削到的那个”水位线”x,再用剩余次数做微调。适用信号:贪心策略显然(每次削最大的),但
k是 1e9 量级 → 把「逐步模拟」换成「二分终点」。
一、题目与关键转化
给定 nums1、nums2(长度均为 n)和 k1、k2。可以把 nums1 中任意元素 ±1 至多 k1 次,nums2 同理至多 k2 次,求最小的差值平方和 Σ (nums1[i] - nums2[i])²。
转化 1:k1 和 k2 完全等价
设 d[i] = |nums1[i] - nums2[i]|。想把这个差值减 1,有两种走法:
| 手段 | 操作 | 消耗 |
|---|---|---|
| 动 nums1 | nums1[i] 朝 nums2[i] 靠一步 |
k1 减 1 |
| 动 nums2 | nums2[i] 朝 nums1[i] 靠一步 |
k2 减 1 |
两条路对 d[i] 的效果一模一样(都是 d[i] -= 1),所以两个预算可以合并:
k = k1 + k2 |
转化 2:问题变成「削峰」
有数组
d[0..n-1](非负),共有k次操作,每次可以把任意一个正数减 1(不能减到负数),最小化Σ d[i]²。
这一步之后就与 nums1/nums2 无关了。
注意:题面允许元素变成负数,这是为了让”朝对方靠拢”永远合法,所以上面的转化没有额外约束。
二、思路演进
阶段一:贪心 + 优先队列(正确,但会超时)
贪心策略:每次把当前最大的 d 减 1。
为什么贪心是对的? 因为 f(x) = x² 是凸函数,边际收益递减:
把 a 减 1 的收益 = a² - (a-1)² = 2a - 1 |
若 a > b,则 2a - 1 > 2b - 1。
交换论证:任何一步没有削当前最大值的方案,把这一步挪去削最大值,结果不会变差。所以”每次削最大”是唯一的最优形态。
class Solution { |
为什么会超时?
| 量 | 上限 |
|---|---|
k = k1 + k2 |
2 × 10⁹ |
n |
10⁵ |
| 单次操作 | O(log n) |
| 总计 | O(k log n) ≈ 3 × 10¹⁰ |
阶段二:二分水位线
关键转变:不要再问「这一步削哪个」,而是问——
所有元素最终会被削到哪条水平线上?
把 d 想象成一片高低不平的地形,k 次操作就是挖土。从最高处往下挖,最后会挖出一个平坦的水位面 x,所有比 x 高的地方都被削到 x,比 x 低的地方原封不动。
只要知道 x 是多少,就能以 O(n) 的时间复杂度直接算出答案,完全不需要模拟。
三、二分水位线:完整推导
1. 定义代价函数 need(x)
need(x) = Σ max(0, d[i] - x) |
含义:把所有大于 x 的差值都压到 x,需要多少次操作。
(d[i] ≤ x 的元素贡献 0,不动它们。)
2. 单调性
x 越大 → 需要削掉的土越少 → need(x) 越小。
x ↑ ⇒ need(x) ↓ (单调不增) |
于是可以用二分答案。
3. 二分目标
找 最小的
x,使得need(x) ≤ k。
x 越小水位越低、代价越高;我们要在”代价不超预算”的前提下把水位压到最低。
4. 二分写法(求最小值模板)
while (lo < hi) { |
| 项 | 值 |
|---|---|
下界 lo |
0 |
上界 hi |
max(d) |
| 返回 | lo(最小的可行水位) |
关于
l = mid/r = mid与取整方向的配套关系,见笔记 二分答案模板 —— 求最大值 vs 求最小值。
5. 拿到 x 之后:剩余次数 rem
long long used = 0; |
关键事实:rem 一定小于 count(d[i] ≥ x)。
证明:因为 x 是最小的可行水位,所以 x - 1 不可行,即 need(x-1) > k。而
need(x - 1) = Σ_{d > x-1} (d - (x-1)) |
代入 need(x-1) > k:
need(x) + count(d ≥ x) > k |
这条不等式的意义:剩余次数不足以把所有 x 都降到 x-1,所以只会有一部分降下去,不会出现”降完了还有剩”的边界麻烦。
6. 最终状态
| 情况 | 数量 | 最终值 |
|---|---|---|
d[i] < x |
— | 保持原值(没碰过) |
d[i] ≥ x |
count |
rem 个 → x - 1;count - rem 个 → x |
挑选哪
rem个降到x-1无所谓:它们此时都是x,降 1 的收益相同。
7. 答案公式
ans = Σ_{d[i] < x} d[i]² + (count - rem) · x² + rem · (x - 1)² |
long long count = 0, sumSqLess = 0; |
代码里不必真的修改
diff数组,统计出count和sumSqLess直接套公式即可。
四、完整代码
class Solution { |
核心代码只有三段
| 段 | 作用 |
|---|---|
| 二分 | 找最小可行水位 x |
求 rem |
k - need(x),剩余微调次数 |
| 套公式 | Σ_{d<x} d² + (count-rem)·x² + rem·(x-1)² |
五、易错点
必须用
long longn ≤ 10⁵,d ≤ 10⁵,平方和可达10⁵ × (10⁵)² = 10¹⁵,int直接溢出。
注意abs((long long)nums1[i] - nums2[i])——先转long long再取绝对值。x = 0必须在入口提前返回 —— 就是这行,长得像剪枝,其实是必需的:if (total <= k) return 0;
total = Σ d[i] = need(0),所以total <= k完全等价于「二分出来的x会是 0」。count统计的是d >= x,不是d > x
原本就等于x的元素同样能被继续降 1,必须算进去。rem一定< count
这是由x的最小性保证的(见第三节证明)。如果你实现出来rem ≥ count,说明二分边界写错了。need要用long long累加
单个need可达10⁵ × 10⁵ = 10¹⁰,int存不下。
六、相关题目
同款「水位线削峰 + 余数微调」:
- 2333. 最小差值平方和(本题)
- 1648. 销售价值减少的颜色球(模型几乎一致:削到水位线 + 余额分配)
二分答案体系(本地笔记):
