【小白笔记】两数之和
def main():nums=list(map(int,input().split()))target=int(input())for i in range(len(nums)):for j in range(i+1,len(nums)):if nums[i]+nums[j]==target:print(i,j)return
if __name__ == "__main__":main()
程序逻辑解释
-
输入部分:
nums = list(map(int, input().split())) target = int(input())- 第一行输入:整数数组(空格分隔)
例:2 7 11 15 - 第二行输入:目标值
例:9
- 第一行输入:整数数组(空格分隔)
-
双重循环查找:
for i in range(len(nums)):for j in range(i + 1, len(nums)):- 从数组的第一个元素开始,逐一和后面的每个元素组合相加。
- 当
nums[i] + nums[j] == target时,输出索引(i, j)并返回。
-
输出部分:
print(i, j) return-
输出一对下标(用空格分隔)
例如输入上面的例子会输出:0 1
-
🧾 示例运行
输入:
2 7 11 15
9
输出:
0 1
解释:
nums[0] + nums[1] = 2 + 7 = 9
⚙️ 复杂度分析
- 时间复杂度:O(n²)
因为有两层循环。 - 空间复杂度:O(1)
只用常量级变量。
对于小数据输入,这个方案没问题。
🚀 进阶优化(哈希表写法)
如果你要让程序更快(O(n) 时间),可以用哈希表(字典)来实现:
def main():nums = list(map(int, input().split()))target = int(input())seen = {}for i, num in enumerate(nums):complement = target - numif complement in seen:print(seen[complement], i)returnseen[num] = iif __name__ == "__main__":main()
解释:
seen字典保存已经访问过的数字及其下标。- 每读一个数,就看看
target - num是否已经出现过。 - 如果出现了,说明找到了这两个数的下标。
✅ 总结
| 写法 | 思路 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|
| 你的版本 | 双重循环 | O(n²) | O(1) |
| 哈希表版本 | 一次遍历 + 查表 | O(n) | O(n) |
Python 编程中最常见、最基础但容易混淆的几个点:
数据结构初始化、enumerate 的语义、单词含义、字典用法、return 的作用。
🧩 问题 1:seen = {} 是字典的初始化吗?
✅ 是的!
在 Python 里:
| 类型 | 初始化写法 | 示例含义 |
|---|---|---|
| 字典 (dict) | seen = {} | 创建一个空字典,用于保存键值对(key-value) |
| 列表 (list) | s = [] | 创建一个空列表,用于保存一组有序元素 |
💡 举例:
seen = {} # 空字典
seen['a'] = 1
seen['b'] = 2
print(seen) # {'a': 1, 'b': 2}s = [] # 空列表
s.append(10)
s.append(20)
print(s) # [10, 20]
🧩 问题 2:enumerate(nums) 的含义
enumerate() 是一个内置函数,它可以在 遍历列表时同时取出索引和值。
💡 举例说明:
nums = [2, 7, 11, 15]
for i, num in enumerate(nums):print(i, num)
输出:
0 2
1 7
2 11
3 15
也就是说:
i表示下标(index)num表示对应的元素值(value)
📘 单词含义
enumerate 英文意思是:
“列举,枚举”。
所以你可以理解为:
“把 nums 里的每个元素和它的下标一起列举出来”。
🧩 问题 3:complement = target - num 中 complement 的意思
complement 这个单词的意思是:
“补数、补充的部分”。
在数学或算法里,它常用于表示“我还需要多少才能凑成目标值”。
💡 举例:
假设:
target = 9
num = 2
那么:
complement = 9 - 2 = 7
意思就是:
“我当前有 2,还需要 7 才能凑成目标值 9。”
所以我们就在字典里查查:
有没有一个之前出现过的数字是 7?
如果有,那就是一对符合条件的数字。
🧩 问题 4:if complement in seen: 和 seen[complement] 的用法
这两句连起来理解最容易👇
if complement in seen:print(seen[complement], i)
解释:
-
if complement in seen:
检查字典seen里是否有键complement。
→ 例如complement = 7,那就检查字典里有没有7这个键。 -
seen[complement]
表示取出这个键对应的值。
在这个程序中,值 = 这个数字的下标。 -
所以这两句的逻辑是:
“如果目标补数已经出现过,就打印它的下标和当前下标。”
💡 举个具体的例子:
假设输入:
nums = [2, 7, 11, 15]
target = 9
运行过程如下:
| i | num | complement | seen | complement in seen? | 输出 |
|---|---|---|---|---|---|
| 0 | 2 | 7 | {} | ❌ 否 | - |
| seen = {2:0} | |||||
| 1 | 7 | 2 | {2:0} | ✅ 是 | 打印 seen[2], i → 0 1 |
输出:
0 1
🧩 问题 5:return 的作用
return 的意思是:
“结束当前函数,并可选择性地返回一个结果。”
在这里,它的作用是:
print(seen[complement], i)
return
当找到一对满足条件的数字后:
- 打印它们的下标;
- 立刻退出
main()函数,不再继续循环。
💡 举例:
def test():for i in range(5):if i == 3:print("found 3")return # 直接退出函数print(i)test()
输出:
0
1
2
found 3
可以看到,return 后面的循环不会再执行。
✅ 总结表
| 概念 | 作用 | 举例 | 英文含义 |
|---|---|---|---|
{} | 创建空字典 | seen = {} | dictionary |
[] | 创建空列表 | s = [] | list |
enumerate() | 同时获取下标和值 | for i, num in enumerate(nums) | 枚举、列举 |
complement | 需要的补数 | target - num | 补数、补充 |
in | 判断是否存在 | if x in seen: | “在……里面” |
return | 退出函数 | return | 返回、结束 |
其实,i、j、k 这三个变量名字并不是缩写,而是历史传统。
在数学中、尤其是矩阵和循环中:
i通常表示 index(索引)j表示 just next index(第二层循环索引)k表示 第三层循环索引
早期的 Fortran、C 语言和数学表达式中常常写:
for i = 1 to nfor j = 1 to nfor k = 1 to n
所以这就是惯例:
👉 i 表示第一层循环,j 表示第二层,k 表示第三层。
它们不是缩写,而是传统约定。
二、理解这段代码的逻辑
代码如下 👇
for i, num in enumerate(nums):complement = target - numif complement in seen:print(seen[complement], i)returnseen[num] = i
我们逐行解释。
1️⃣ for i, num in enumerate(nums):
enumerate(nums) 作用是:
- 同时获取 索引 i 和 元素 num。
例如:
nums = [2, 7, 11, 15]
for i, num in enumerate(nums):print(i, num)
输出:
0 2
1 7
2 11
3 15
2️⃣ complement = target - num
计算“目标数缺多少”。
比如目标是 9,当前数字是 2,
那就需要一个 7 来凑成 9。
所以 complement = 9 - 2 = 7。
3️⃣ if complement in seen:
这里的 seen 是一个 字典(dict),用来记住之前出现过的数字及其索引。
这句话的意思是:
“如果我现在需要的那个补数(complement)之前已经见过了,那我就找到了答案。”
举例:
-
遍历到第一个数
2时,seen是空的。 -
遍历到第二个数
7时:- complement = 9 - 7 = 2
- 发现 2 在 seen 里!说明 2 和 7 可以组成目标 9。
4️⃣ print(seen[complement], i)
打印出两个索引:
seen[complement]是之前存的补数的索引;i是当前数字的索引。
比如:
nums = [2, 7, 11, 15], target = 9
执行过程:
| i | num | complement | seen | 结果 |
|---|---|---|---|---|
| 0 | 2 | 7 | {} | 无 |
| seen[2] = 0 | ||||
| 1 | 7 | 2 | {2: 0} | 找到 (0,1) |
输出:
0 1
5️⃣ seen[num] = i
如果没找到补数,就把当前数字和它的索引加入字典。
这一步体现了提到的逻辑:
✅ “边填充字典边判断”
即:
- 每遇到一个数
num,先看看它的“补数”是不是已经在字典里; - 如果在,那说明之前那个补数 + 当前这个数 = target;
- 如果不在,那把当前这个数记下来,留给后面的数来匹配。
✅ 总结整个思路:
-
初始化空字典
seen = {}; -
遍历数组:
- 计算当前数的补数;
- 如果补数已经见过 → 立即返回答案;
- 否则把当前数加入字典;
-
时间复杂度:O(n)。
💡一句话总结:
这段代码的核心思想是 用字典记录之前见过的数,边走边查是否能配对成目标值。
不需要先“全部填充字典”,而是 遍历时即查即存。
