从题意写出形式化规格
约 20 分钟
开课第一件事:先确认我们在解同一道题
这是刷题课第一课,没有前课要补。面试中最可惜的失败不是不会算法,而是题目要“返回下标”,你却返回了两个数值。老师用 Two Sum 带你把自然语言压成可检查的规格,再开始写代码。
题目说:给定整数数组 nums 和整数 target,找出两个不同位置,使它们的元素之和等于 target。假设恰好有一组答案,返回两个下标。
把它拆成四类信息:
- 输入域与规模:
nums是整数数组,长度至少 2;target 是整数。 - 输出关系:返回
[i,j],满足i != j且nums[i]+nums[j]==target。 - 边界与无解约定:本题保证唯一解,因此不用定义无解返回值;允许负数和重复值。
- 资源限制:若 n 很大,
O(n²)双循环可能超时,目标是O(n)时间、O(n)空间。
从规格推导算法,而不是背模板
扫描到值 x 时,我们需要知道此前是否出现过 target-x。哈希表保存“值→下标”。以 nums=[2,7,11,15],target=9 为例:
| i | x | 需要 | 表(检查前) | 动作 |
|---|---|---|---|---|
| 0 | 2 | 7 | {} | 存 2→0 |
| 1 | 7 | 2 | {2:0} | 找到,返回 [0,1] |
必须先查再存。若输入 [3,3]、target=6,第一轮存 3→0,第二轮才能用不同下标匹配;若先存再查,第一轮可能错误地让一个元素和自己配对。
def two_sum(nums, target):
seen = {}
for i, x in enumerate(nums):
need = target - x
if need in seen:
return [seen[need], i]
seen[x] = i
raise ValueError("题目约定被破坏:不存在答案")
assert two_sum([2, 7, 11, 15], 9) == [0, 1]
assert two_sum([3, 3], 6) == [0, 1]
assert two_sum([-4, 9, 1], 5) == [0, 1]
不变量是:进入第 i 轮检查时,seen 含有下标 0 到 i-1 的值和下标。找到 need 时,旧下标一定不同于 i;若没找到,加入当前值后不变量继续成立。每个元素只处理一次,平均时间 O(n),额外空间 O(n)。
易错纠正与当堂练习
- 返回
[2,7]是返回数值,不是下标。 - 不能默认元素为正数;滑动窗口在含负数时没有这里需要的单调性。
- 重复值不是重复使用同一元素,关键看下标是否不同。
请把题目改成“可能无解,返回空数组;可能有多组,返回任意一组”,先改规格,再改函数。随后测试空数组、[3,3]、含负数和无解输入。只有测试与约定一致,代码才算完成。
下课预告
下一课会学习循环不变量:它不是写给阅卷人的装饰,而是解释“为什么每轮扫描后仍没有漏掉答案”的工具。
出口自检
合上正文后完成三项验收:一,用自己的话解释“从题意写出形式化规格”解决什么问题;二,闭卷写出状态定义、不变量、复杂度和至少三个边界测试,再运行代码核对;三,写出一个会使当前方法失效或答案改变的条件。随后打开正文逐项核对。任何一项说不清,就回到对应证据或步骤修正,而不是继续背结论。
Practice
本课练习
先独立作答再提交;编程题会在隔离沙箱中真实编译、运行并对拍。