今天继续写刷题系列第九篇,这次聊的是力扣第 3371 题,题面里带“Outlier”这个词,中文一般叫异常值,程序函数名是 getLargestOutlier。这题并不是那种一上来就让人眼前一亮的难题,但它很典型:表面看是一道构造背景题,实际上一旦把题目条件提炼成一条等式,解法就变成了“移项变形 + 哈希表查值”,整个过程写下来不超过二十行。如果你最近在准备算法面试,或者正在刷哈希表、计数类题目,这篇值得花十分钟看完,因为里面用到的“枚举右,维护左”思路,在很多题目里都能复用。
先说这题适合什么基础的人。哪怕你只是刚学完哈希表,也可以直接读;如果已经刷过两数之和,那理解起来更快。全文我不会堆一堆没用的题号,重点放在两件事上:一是如何把题目里的隐藏关系转成代数式,二是为什么这种“枚举候选值 + 临时删除当前值”的写法能成立。理解完之后,你自己就能写出一份能通过的代码。
1. 力扣3371题目到底在说什么
1.1 如果不做数学翻译,第一反应会走进哪些死胡同
第一次看这道题的经历比较典型。题面说的是:数组里有一个原始值 original,这个值本身会作为数组里的一个元素出现;除它之外的其他元素里面,有一个异常值 outlier;剩下的所有元素加起来,刚好又等于 original。现在要我们找 outlier 的最大可能值。
很多人读完题面后的第一反应是枚举:先选一个位置当 outlier,再选一个位置当 original,然后检查其他数字是否能分成若干组、组的和等于 original。这个方向一深入就会撞墙,因为“检查其他数字能否相加成某个目标值”本质上是一个子集和问题,指数级复杂度,数组长度稍微大一点就直接不可行。
就算只做两两枚举,只验证“总和满足两倍关系”,也容易踩同一个坑:original 和 outlier 数值相同的时候怎么办?两个角色会不会用的是同一个数组元素?如果脑子里没有一张清晰的执行图,写出来的代码很容易出现“自问自答”的错误。所以这道题真正值得学的,不是暴力搜索,而是怎么把题面里的构造成分压缩成一个可以查表的条件。
1.2 把构造过程翻译成一条等式
先给变量起个名字,让后面的推理清爽一点。
记整个数组的和为 S,候选的异常值为 x,原始值为 o。按照题面的构造
