python基础语法命令(C程序员刷leetcode)
平时编码都用 C,但 C 刷算法效率太低(手写哈希/手动内存管理/一个反转字符串要写 5 行)。Python 把该省的事全省了。本文目标是:有 C 基础 + 零 Python 经验 → 读完能直接上 LeetCode 做题。
算法基础练习总结入口:我的算法地图
文章目录
一、背景:为什么要从 C 切换到 Python 刷题
1. C vs Python 刷题差异总览—一张表看清省了什么、代价是什么
Python 刷题比 C 少写 3-5 倍代码,这个压缩率不是来自更短的语法,而是"不需要写的东西变少了"。下面这张表列出刷题场景下 C 和 Python 的差异——关注"省了什么"那一列。
| 特性 | C | Python | 省了什么 |
|---|---|---|---|
| 变量 | int a; 声明类型 |
a = 10 |
类型声明 |
| 语句结尾 | 必须 ; |
无 | 分号 |
| 代码块 | {} 大括号 |
缩进(4 空格) | 括号 |
| 编译 | 编译→链接→运行 | python3 a.py 直跑 |
编译步骤 |
| 内存 | malloc/free 手动管理 |
Python 自动回收不用管 | 忘掉 free() |
| 数据结构 | 手写哈希/堆/树 | dict/set/heapq/deque 内置 |
全部实现代码 |
| 代码量 | 100 行 | 15-30 行 | 3-5x 压缩 |
核心结论:算法题 90% 的场景中,Python 的代价可忽略,省下的代码量是实打实的。考试上机写 Python,写完 C 选手还在调 realloc。
2. Python 刷题的核心哲学—代码是解法的直译,不是在"编程"
C 写算法是"指挥计算机怎么做"——分配内存、管理指针、写循环。Python 写算法是"描述解法本身"。
C: 想好解法 → 翻译成 C 的数据结构 → 手写辅助逻辑 → 排内存 bug → 得到答案
Python:想好解法 → 用内置容器描述 → 得到答案
中间两步被语言本身吃掉了。这就是代码量 3-5x 压缩的根源——不是语法更短,是少写了两个中间层。学习目标从"管理内存和指针"转变为"用内置工具拼装解法"。
3. 刷题需要的 Python 工具箱速览—一句话记住全景图
本文只覆盖以下内容,这些是算法题 Python 代码中 95% 出现的元素。不讲面向对象、不讲装饰器原理、不讲异步——刷题用不上。
基础语法层 list → dict → str → set → tuple ← 四、基础容器
专用工具层 deque → heapq → bisect ← 五、专用工具
辅助技能层 input() / print() / f-string ← 三、输入输出
避坑 + 模板 C 程序员高频踩坑 → 实战模板 ← 六、七
“改造” C 程序员的习惯,不是学新语法,是忘记旧语法——忘掉
malloc、忘掉strcpy、忘掉手写哈希。Python 的世界里,这些东西根本不存在。
二、基础语法:C 程序员最关心的差异—数据类型、控制流、函数
只用对照表 + 最简代码示例,不给完整语法教程。目标:3 分钟能看懂 Python 代码。
1. 基础数据类型—int / float / bool / None / str,五种就够
变量无需声明,类型跟着值走。Python 是动态类型语言——变量只是名字,类型存在值身上。a = 10 之后 a 指向一个整数,a = "hello" 之后同一个 a 指向一个字符串。不需要 int a; 声明,也不需要关心这个变量"是什么类型"——拿来用就行。
| 类型 | 写法 | C 等价 | 值得注意的区别 |
|---|---|---|---|
| 整数 | a = 10 |
int a = 10; |
无限大,超出 32 位不会溢出;3/2 结果是 1.5(不是 1);整除用 // |
| 浮点 | b = 3.14 |
double b = 3.14; |
IEEE 754,和 C 行为一致 |
| 布尔 | t = True / f = False |
bool t = true; |
首字母大写,不是 true/false |
| 空值 | x = None |
NULL |
首字母大写,不是 NULL |
| 字符串 | s = "hello" |
char s[] = "hello"; |
不可变,不能 s[0]='x';切片 s[::-1] 一行反转(详见 4.3) |
类型转换:int("42") 字符串→整数,str(42) 整数→字符串,float("3.14") 字符串→浮点。不用记 atoi/itoa。
2. 数字运算—/ 总是浮点结果,// 才是整除,** 是乘方
数字运算的规则由参与运算的值决定。Python 遇到 3/2,两个操作数都是整数,但 / 运算符保证返回浮点——结果类型由运算符决定,不是由操作数决定。+ - * 则相反:整数 + 整数 = 整数,只要有一个浮点就返回浮点。动态类型的核心影响就这一个:你不声明类型,结果的类型由运算规则产生。
| 运算符 | 含义 | C 等价 | 注意 |
|---|---|---|---|
+ - * |
加减乘 | 完全一样 | — |
/ |
除法 | — | 结果总是浮点,3/2 = 1.5 |
// |
整除(地板除) | /(整数除法) |
3//2 = 1,-3//2 = -2(向下取整,不是向零) |
% |
取模 | % |
-7%3 = 2(结果符号同除数,C 是 -1) |
** |
乘方 | pow() |
2**10 = 1024 |
int 不溢出:Python 整数可以任意大,2**100 算完直接打印,不需要 long long 也不需要手写大数。
八个内置函数速查:
| 函数 | 作用 | 示例 | 说明 |
|---|---|---|---|
abs(x) |
绝对值 | abs(-5) → 5 |
— |
round(x) |
四舍五入 | round(3.6) → 4 |
— |
pow(x, y) |
乘方 | pow(2, 10) → 1024 |
等效 x**y |
max(a, b) |
取大 | max(1, 5) → 5 |
也可传 list:max([1,3,5]) |
min(a, b) |
取小 | min(1, 5) → 1 |
同上 |
sum(arr) |
求和 | sum([1,2,3]) → 6 |
传可迭代对象,比手写 for 累加快 |
all(iter) |
全为真 | all([True, True]) → True |
配合生成器做校验 |
any(iter) |
任一为真 | any([False, True]) → True |
同上 |
3. 控制流—and/or/not 替代 &&/||/!,缩进替代 {},for 只有 range 一种写法
Python 的控制流和 C 逻辑完全一样,只是写法不同。三个核心变化:① &&/||/! 变成 and/or/not——读起来更像英语;② {} 变成缩进——代码块边界由缩进层级决定,不是大括号;③ for 循环只有 for i in range(n) 一种——没有 C 的三段式 for(;;)。
条件判断:
| 场景 | C | Python |
|---|---|---|
| 基本 if | if (a > 0) { ... } |
if a > 0: |
| 否则如果 | else if (a == 0) { ... } |
elif a == 0: |
| 否则 | else { ... } |
else: |
| 逻辑与 | (a > 0) && (b < 10) |
a > 0 and b < 10 |
| 逻辑或 | `(a > 0) | |
| 逻辑非 | !flag |
not flag |
循环:
| 场景 | C | Python | 说明 |
|---|---|---|---|
| 基础 for | for (int i=0; i<n; i++) |
for i in range(n): |
i 取 0 到 n-1 |
| 带步长 | for (int i=0; i<n; i+=2) |
for i in range(0, n, 2): |
步长放第三个参数 |
| 倒序 | for (int i=n-1; i>=0; i--) |
for i in range(n-1, -1, -1): |
倒序用负步长 |
| while | while (a > 0) { ... } |
while a > 0: |
去掉括号 |
| 中断/继续 | break / continue |
完全一样 | — |
| do-while | do { ... } while (...) |
Python 没有 | 改用 while True: + if 条件: break |
一句话总结:C 的 for (;;) 只有一种等价物——for i in range(n):。range(start, stop, step) 是 Python 最常用的内置函数之一,含义和 C 的 for 循环三要素一一对应。
4. 函数—不声明类型 + 多返回值
Python 函数用 def 定义,不声明参数类型和返回值类型。和 C 的核心差异就两条——参数和返回值不写类型(靠约定),一个函数可以返回多个值(本质返回 tuple)。
| C | Python |
|---|---|
int add(int a, int b) { return a + b; } |
def add(a, b): return a + b |
C 程序员最需要适应的两点:
① 类型全靠约定:参数不写类型,返回值不写类型。代码短了,但 IDE 类型提示比 C 弱,名字起好是关键。
② 多返回值——一行解包:
def min_max(arr):
return min(arr), max(arr)
lo, hi = min_max(nums) # 同时接收两个返回值
C 要做到同样的事,要么传两个指针当出参,要么定义一个 struct MinMax 返回。Python 原生支持多返回值(本质返回 tuple),算法题中极高频——比如 BFS 同时返回深度和节点。
基础语法这一章,每节约一张对照表。合起来 10 张不到——覆盖了 C 程序员最需要知道的全部语法差异。背 C 语法花了几年,Python 只需要记住这几张表。
三、输入输出—极简但 ACM 模式要知道几招
1. C vs Python 输入输出对照—scanf/printf → input/print,一张表覆盖语法迁移
Python 的输入输出比 C 简单一个数量级——不记格式符、不取地址、不区分类型。C 的 scanf 需要 %d/%f/%s 指定类型,printf 需要同样格式符写出。Python 只有两个函数:input() 负责读,print() 负责写,格式全部靠 split() 和 f-string 搞定。
| 序号 | 操作 | C | Python | 要点 |
|---|---|---|---|---|
| 1 | 头文件 | #include <stdio.h> |
无需(或 import sys) |
裸写 print("hello") 就能跑 |
| 2 | 输入整数 | scanf("%d", &a) |
a = int(input()) |
手动转类型 |
| 3 | 输入多个 | scanf("%d %f", &a, &b) |
a, b = map(int, input().split()) |
链式操作一行 |
| 4 | 输出 | printf("a=%d", a) |
print(f"a={a}") |
f-string 比 %d 直觉得多 |
| 5 | 不换行 | printf(".") |
print(".", end="") |
参数控制 |
| 6 | 性能 | 最快 | 大输入慢,换 sys.stdin |
99% 的题不卡常 |
2. 基础输入 input()—读一行字符串,剩下的自己拆
input() 和 C 的 scanf 有本质区别——它只做一件事:从标准输入读一行,返回字符串。不解析类型、不跳过空白、不停在空格。 要什么类型,拿到字符串之后自己 split() + int() 手动转。这比 scanf 麻烦一步,但换来的是完全的解析控制权——想按什么规则拆就按什么规则拆。
# 最高频写法——两个整数
a, b = map(int, input().split()) # 输入 "3 5" → a=3, b=5
# 读一个整数
n = int(input())
# 读一行整数到列表
arr = list(map(int, input().split())) # 输入 "1 2 3 4" → [1, 2, 3, 4]
# 读到 EOF
while True:
try:
line = input()
except EOFError:
break
三个要点:split() 默认按空白符切割 / map(int, ...) 对每个元素执行 int() / 读不到数据时抛出 EOFError
3. 基础输出 print()—默认换行,用参数调行为
print() 和 C 的 printf 最大区别——不记格式符。print() 把传入的所有参数用空格拼接,末尾自动加换行。想调格式?用 end 改换行、用 sep 改分隔符、用 f-string 做格式化——三个参数覆盖了 printf 全部功能且不用记 %d/%f/%s。
x = 42
a, b = 1, 2
print(x) # 输出: 42(然后换行)
print(x, end='') # 输出: 42(不换行,下个 print 接在后面)
print(a, b) # 输出: 1 2(默认空格分隔)
print(a, b, sep='|') # 输出: 1|2
print(f"a={a}, b={b}") # 输出: a=1, b=2
print(f"x/3 = {x/3:.2f}") # 输出: x/3 = 14.00(保留两位小数)
f-string 是 Python 的 printf:f"{变量:格式}"。{x:.2f} 保留两位小数,{x:10d} 定宽对齐。比 %d/%f 直观——变量直接写花括号里,不用记格式符顺序。
4. ACM 四种输入模式—读到 EOF / 先读组数 / 不定长 / 混合输入,四段模板复制即用
LeetCode 不用自己写输入——函数参数就是解析好的数据。但 ACM 模式/牛客网/公司笔试需要自己从标准输入读数据。四种模式覆盖了所有常见输入格式,直接复制模板改变量名就能用。
| 模式 | Python 模板 | 典型题目 |
|---|---|---|
| 读到 EOF | for line in sys.stdin: |
A+B 多组数据 |
| 先读组数 | for _ in range(int(input())): |
第一行给 N,后面 N 组 |
| 每行不定长 | nums = list(map(int, input().split())) |
每行数字个数不同 |
| 混合输入 | n, s = input().split(); n = int(n) |
一行里既有数字又有字符串 |
# 模板 1:读到 EOF(最常用)
# 输入示例:
# 1 2
# 3 4
# 5 6
# (没有结束标志,读到文件末尾就停)
import sys
for line in sys.stdin:
a, b = map(int, line.split())
print(a + b)
# 模板 2:先告知组数
# 输入示例:
# 3 ← 接下来有 3 组
# 1 2
# 3 4
# 5 6
for _ in range(int(input())):
a, b = map(int, input().split())
print(a + b)
# 模板 3:每行不定长
# 输入示例:
# 1 2 3
# 4 5
# 6 7 8 9
# (每行数字个数不固定,求每行和)
while True:
try:
nums = list(map(int, input().split()))
print(sum(nums))
except EOFError:
break
# 模板 4:混合输入(数字 + 字符串)
# 输入示例:
# 3 hello ← 重复 3 次 "hello"
# 输出:
# hellohellohello
n, s = input().split()
n = int(n)
for _ in range(n):
print(s, end='')
“ACM 输入的本质是字符串解析。Python 不需要格式符,只需要
split()+int()。C 程序员最不适应的是——没有scanf的自动类型推导,但换来的是无上限的灵活性。”
四、基础容器—list / dict / str / set / tuple,搞懂这五个 = 算法题不卡壳
算法题 Python 代码里 90% 的数据都是在摆弄这五个容器。每节一张 C vs Python 对比表 + 初始化/访问/常用操作 + 一段 blockquote 收尾。
1. list——万能动态数组,malloc+realloc 换成一个 []
list 是 Python 的动态数组,自动扩容、可存任意类型、支持下标随机访问。C 里数组要么定长要么 malloc,扩容要手写 realloc。Python 的 list 只管 append——撑满了自己扩,缩了自己收。元素类型也可以混着放:[1, "hello", 3.14] 合法。
| 特性 | C (malloc) | Python (list) | 优势 |
|---|---|---|---|
| 内存 | 手动分配释放 | 自动管理 | 忘掉 malloc/free |
| 大小 | 自己维护 len 变量 | len(arr) |
直接获取 |
| 扩容 | realloc 手动 |
.append() 自动 |
只管加,不管容量 |
| 操作 | 手写 for 循环 | 切片 + 列表推导 | 一行顶 C 十行 |
# 初始化
arr = [] # 空列表
arr = [0] * 10 # 10 个 0
arr = [1, 2, 3, 4, 5] # 字面量
arr = [i for i in range(10)] # 列表推导:[0,1,2,...,9]
arr = [x for x in arr if x > 0] # 带过滤:[2,4,6,...]
# 基本属性
len(arr) # 长度(所有容器统一写法,不是 arr.len())
if not arr: # 判空——空列表为 False
print("empty")
# 访问——负索引是 C 没有的
arr[0] # 第一个
arr[-1] # 最后一个(C 做不到)
arr[1:3] # 切片:[arr[1], arr[2]]
# 遍历
for x in arr: # 只取值
print(x)
for i, x in enumerate(arr): # 取值 + 索引
print(f"arr[{i}] = {x}")
# 常用操作
arr.append(6) # 尾部加 O(1)
arr.pop() # 尾部弹 O(1)
arr.pop(2) # 指定位置弹 O(n)
arr.sort() # 升序
arr.sort(key=lambda x: x[1]) # 按第二个元素排序
arr.sort(reverse=True) # 降序
arr.reverse() # 原地反转
arr.insert(2, 100) # 位置 2 插入 O(n)
arr.remove(100) # 按值删除 O(n)
列表推导式——C 程序员第一次见会愣住的语法。一个完整的 for 循环 + 条件判断 + 变换操作,压成一行:
# C: 取数组里所有偶数,平方,放到新数组
# 需要 malloc + for + if + 赋值,至少 6 行
# Python:
squares = [x*x for x in arr if x % 2 == 0]
二维列表——避坑:
# ✅ 正确写法——每行独立
dp = [[0] * n for _ in range(m)] # m 行 n 列,每行是独立的新 list
dp[0][0] = 1 # 只改第一行第一列,其余行不变
# ❌ 陷阱写法——三行指向同一个 list!
dp = [[0] * 3] * 3
# 等价于:
# row = [0, 0, 0]
# dp = [row, row, row] ← 三个元素都是 row 的引用!
dp[0][0] = 1 # dp 变成 [[1,0,0], [1,0,0], [1,0,0]]——三行全变了!
“C 里写动态数组:
mallocn 个 → 满了realloc2n → 复制 → 释放旧空间。Python 里写动态数组:arr.append(x)。”
2. dict——哈希表,手写哈希函数 → {} 直接用
dict 是 Python 内置的哈希表,键值对(key → value)映射,插入和查找全部 O(1)。C 要么手写哈希函数加冲突链,要么用 uthash 这种第三方宏——不管哪种都是不小的工程。Python 直接用 {} 或者 dict(),背后是优化过的工业级哈希表实现。
| 特性 | C (手写) | Python dict | 优势 |
|---|---|---|---|
| 实现 | 手写哈希函数+冲突链 | 内置 {} |
无需造轮子 |
| 插入 | 手写冲突处理 | d[key] = val |
一行 |
| 查找 | 手写遍历 | d.get(key, 0) O(1) |
即时 |
| 遍历 | 手写 | for k, v in d.items(): |
一行 |
# 初始化
d = {} # 空字典
d = {"a": 1, "b": 2} # 字面量
# 基本属性
len(d) # key 的个数
# 增删查
d["c"] = 3 # 增
d["a"] = 100 # 改
del d["b"] # 删
if "a" in d: # 查是否存在
print("found")
val = d.get("z", 0) # 安全查:有则返回,无则返回 0
# 遍历
for k, v in d.items(): # 最常用
print(f"{k} -> {v}")
for k in d: # 只遍历 key
print(k)
dict 本身只有增删查,但 Python 提供了两个高频变体,算法题里比裸 dict 更常用。
进阶 1:Counter——一行频次统计:
from collections import Counter
arr = [1, 1, 2, 2, 2, 3]
cnt = Counter(arr) # {1: 2, 2: 3, 3: 1}
cnt.most_common(1) # [(2, 3)] 最高频 Top1
C 要统计数组频次——定义哈希表、初始化、遍历、插入或自增。Counter 一行搞定,后面直接当 dict 用。
进阶 2:defaultdict——省去"这个 key 存在吗"的判断:
from collections import defaultdict
groups = defaultdict(list) # 不存在的 key 自动创建空 list
for word in words:
groups[word[0]].append(word) # 不需要 if word[0] not in groups
“C 程序员面试最怕手写哈希表。Python 程序员从
d = {}开始解题。这就是差距。”
3. str——不可变但切片是杀手级武器
Python 字符串是一个不可变的字符序列。它和 C 的 char[] 有本质区别——不是"字符的数组",而是"一个整体值"。
三个核心特征:①不可变——一旦创建就不能原地修改,s[0] = 'x' 会报错,任何修改都返回一个新字符串;②自动管理——不需要分配固定大小,不用管缓冲区;③自带长度——len(s) 是 O(1) 的直接取值,不是 strlen 的遍历。
| 特性 | C (char[]) | Python (str) | 优势 |
|---|---|---|---|
| 内存 | 手动分配固定大小 | 自动管理 | 无缓冲区溢出 |
| 长度 | strlen() 遍历 O(n) |
len(s) O(1) |
即时 |
| 赋值 | strcpy() |
s = "hello" |
直接 = |
| 拼接 | strcat() |
s1 + s2 |
运算符 |
| 比较 | strcmp() |
s1 == s2 |
像比数字 |
| 切片 | 不存在 | s[2:5] s[::-1] |
革命性 |
s = "Hello World"
# 基本属性
len(s) # 11——O(1) 直接取值,不是 C 的 strlen 遍历
if not s: # 判空
print("empty")
# 切片——Python 字符串最强大的操作
s[0:5] # "Hello"——从 0 到 5(不含 5)
s[6:] # "World"——从 6 到末尾
s[:5] # "Hello"——从头到 5
s[::-1] # "dlroW olleH"——反转!C 需要 5 行 for 循环
# 高频方法
s.split() # ["Hello", "World"]——按空白分割
s.split(",") # 按逗号分割
" ".join(["a","b"]) # "a b"——用空格拼接列表
s.find("World") # 6——返回位置,找不到返回 -1
s.replace("Hello","Hi") # "Hi World"
s.startswith("He") # True
s.endswith("ld") # True
s.strip() # 去除前后空白
# 字符判断——回文/数字题常用
"123".isdigit() # True
"abc".isalpha() # True
"a1b2".isalnum() # True
注意:str 是不可变对象。s[0] = 'h' 会报错!要改只能构造新字符串:s = 'h' + s[1:]。
“C 程序员对字符串最深的恐惧是缓冲区溢出。Python 的解决方案——让你根本不知道什么叫缓冲区。”
4. set——去重和 O(1) 查找,比 C 的 bool 数组更通用
set 是一个无序、不重复的元素集合,背后是哈希表。插入、删除、查找全部 O(1)。和 C 最大差异:C 要判断一个值出现过没有,要么遍历数组 O(n),要么用 bool 数组打标记——bool 数组要求你提前知道值的范围。set 不在乎值多大、什么类型。
s = {1, 2, 3} # 字面量初始化
s = set() # 空集合(注意 {} 是 dict 不是 set)
len(s) # 元素个数
if not s: # 判空
print("empty")
s.add(4) # 加 O(1)
s.remove(3) # 删(不存在则报错)
s.discard(3) # 安全删(不存在不报错)
2 in s # O(1) 查找——True
# 集合运算——运算符直接写
a = {1, 2, 3}
b = {2, 3, 4}
a & b # {2, 3} 交集
a | b # {1, 2, 3, 4} 并集
a - b # {1} 差集
# 一行判重
has_dup = len(set(arr)) < len(arr)
和 C 的关键差异:C 判断元素存在要么遍历 O(n),要么用 bool 数组标记——bool 数组要求值域已知且连续。Python 的 set 不在乎值多大什么类型,x in s 永远 O(1)。
“C 的 bool 数组标记法要求你知道值的范围。Python 的 set 不在乎——无论值多大、什么类型,
x in s永远 O(1)。”
5. tuple——不可变轻量序列,pair<int,int> 的 Python 版
tuple 和 list 长得一样,唯一的区别——不可变。创建之后不能增、不能删、不能改元素。这恰好让它能做 dict 的 key(list 不行——因为可变就不能哈希)。C 里最像它的是 pair<int,int>,但 tuple 可以有任意多个元素。
t = (1, 2) # 创建
t = 1, 2 # 括号可省略
a, b = t # 解包:a=1, b=2
a, b = b, a # 一行交换两个变量的值
len(t) # 2——和 list 一样用 len()
# 为什么需要 tuple?
# → 不可变 = 可哈希 → 能做 dict 的 key,list 不行
d = {(1, 2): "point"} # ✅ tuple 做 key
d = {[1, 2]: "point"} # ❌ list 不能做 key,会报错
# 多返回值本质就是 tuple
def f():
return 1, 2, 3 # 等价于 return (1, 2, 3)
“tuple 存在的唯一理由——不可变。不可变 = 可哈希 = 能做 dict 的 key。list 做不到这一点。”
五、专用工具—deque / heapq / bisect,特定场景用对工具
基础容器搞定了 90% 的需求。剩下三种情况需要专用工具——队列、堆、二分。
1. deque——双端队列,BFS 标配,list.pop(0) 的 O(1) 替代
deque(double-ended queue)是一个双端队列,头尾插入删除都 O(1)。list 也能当队列用,但 list.pop(0) 是 O(n)——所有元素要往前挪。BFS、滑动窗口这类场景必须用 deque,否则数据量大直接超时。
from collections import deque
q = deque()
# 队列操作(先进先出)—— BFS 标准写法
q.append(1) # 入队尾 O(1)
q.append(2)
q.popleft() # 出队首 O(1) → 1
# 栈操作——直接用 list 更简单
stack = []
stack.append(1) # 压栈
stack.pop() # 弹栈 → 1
核心提醒:BFS 用队列时,千万不要用 list.pop(0)——那是 O(n),数据量大的时候直接超时。deque.popleft() 才是 O(1)。
# BFS 层序遍历模板
def bfs(root):
q = deque([root])
while q:
node = q.popleft()
for child in node.children:
q.append(child)
“Python 不给 stack 和 queue 单独的类型,不是不重要,而是 list 和 deque 已经做得足够好。”
2. heapq——堆,TopK 三行搞定,但只有小顶堆
heapq 是一个基于 list 的堆实现,默认是最小堆——堆顶(heap[0])永远是最小元素。压入弹出均 O(log n),原地建堆 O(n)。Python 没有最大堆(C++ 的 priority_queue 默认大顶),需要大顶堆时——元素取个负就行。
import heapq
heap = []
heapq.heappush(heap, 3) # 压入
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)
heapq.heappop(heap) # 弹出最小的 → 1
# 原地建堆 O(n)
arr = [3, 1, 4, 1, 5]
heapq.heapify(arr)
# TopK——一行取最小/最大 K 个
heapq.nsmallest(3, arr) # 最小的 3 个
heapq.nlargest(3, arr) # 最大的 3 个
大顶堆——Python 只有小顶堆,大顶堆 = 取负:
heapq.heappush(heap, -x) # 压入取负
val = -heapq.heappop(heap) # 弹出再取负 → 原来的最大值
“C 手写堆要维护数组、上浮、下沉。Python heapq 只记一条——默认是小顶堆,大顶堆取个负就行。”
3. bisect——二分查找直接用,不用手写 left/right/mid 边界
bisect 模块提供标准的二分查找——在已排序数组中查找插入位置,O(log n)。二分查找思路不难,难的是 left/right/mid 边界处理(<= 还是 <、mid+1 还是 mid)。bisect 把边界问题全封在库内,你只负责传数组和目标值。
import bisect
arr = [1, 3, 5, 7, 9]
bisect.bisect_left(arr, 5) # 2 → 第一个 >= 5 的位置(lower_bound)
bisect.bisect_right(arr, 5) # 3 → 第一个 > 5 的位置(upper_bound)
# 在有序数组中插入并保持有序
bisect.insort_left(arr, 6) # arr → [1, 3, 5, 6, 7, 9]
“二分查找的难点从来不是思路,是边界。Python 说——‘边界的事交给库,你只负责传数组和目标值’。”
六、C → Python 避坑指南—15 个高频踩坑,一表速查
| # | 分类 | 坑 | C 习惯 | Python 实际行为 | 正确写法 |
|---|---|---|---|---|---|
| 1 | 语义 | 整数除法 | 3/2 = 1(截断) |
3/2 = 1.5(浮点) |
3 // 2 |
| 2 | 语义 | = 是引用 |
= 拷贝值 |
a = b 后改 a[0],b[0] 也变 |
a = b[:] 或 a = b.copy() |
| 3 | 语义 | for 内改循环变量 | for(i=0;i<n;i++) i=10 生效 |
for i in range(n): i=10 无效 |
改用 while |
| 4 | 语义 | 负索引不报错 | 越界 = 段错误 | arr[-1] 静默取最后一个 |
检查索引范围,警惕负值 |
| 5 | 语义 | 字符不能直接加减 | 'a' + 1 得到 'b' |
'a' + 1 → TypeError |
chr(ord(c) - ord('A') + ord('a')) |
| 6 | 容器 | 字符串不可变 | s[0] = 'x' |
报错! str 不可原地改 |
s = 'x' + s[1:];多处修改用 list(s) → ''.join() |
| 7 | 容器 | * 复制二维列表 |
— | [[0]*3]*3 三行指向同一 list |
[[0]*3 for _ in range(3)] |
| 8 | 容器 | list() 构造 |
list(1,2,3) 直觉 |
list() 只接收一个可迭代参数 → TypeError |
[1, 2, 3] 直接字面量 |
| 9 | 容器 | 获取长度 | arr.length() / d.size() |
没有 .len() 方法 |
len(arr) / len(d) 统一内置函数 |
| 10 | 容器 | 循环中删元素 | — | for x in arr: arr.remove(x) 跳元素 |
arr = [x for x in arr if cond] |
| 11 | 函数 | 默认参数陷阱 | — | def f(arr=[]) 所有调用共享同一 list |
def f(arr=None): + 内部判空初始化 |
| 12 | 函数 | 类内方法缺 self |
— | 方法无 self → 参数错位;调用不加 self. → NameError |
def f(self, x): / self.f(x) |
| 13 | 函数 | 返回值类型 | 编译器检查类型 | Python 不检查 → 题目要 int 你却返回 [],WA |
检查题目要求,特别注意空情况的返回值 |
| 14 | 编译 | 拼写错误 | 编译期报 undefined reference |
运行到该行才抛 NameError |
写完立刻跑示例用例;提交前检查变量名 |
| 15 | 编译 | 循环标记未重置 | 每次循环 int i=0 显式初始化 |
外层 flag 被内层改了,下一轮带着旧值跑 | 每轮外层循环开始前手动重置标记 |
“这些坑的共同特征——C 程序员的肌肉记忆在 Python 中不起作用,甚至反向作用。提前知道比 debug 时发现高效得多。”
七、实战速成模板—4 条即查即用
前六章是"查"的,这章是"套"的。每条模板解决一个刷题最高频的重复操作。
7.1 ASCII 字符频次统计
cnt = [0] * 256 # 或 [0] * 128(只含标准 ASCII)
cnt[ord(c)] += 1 # 遍历 s 计数
cnt[ord(c)] -= 1 # 遍历 t 抵消
# 最后 all(v == 0 for v in cnt) → 字母异位词 / 回文排列
C 里
char直接就是整数下标。Python 的字符不能做下标,必须ord()中转。
7.2 字符串修改标准三步
arr = list(s) # ① str → list(str 不可变,list 可下标赋值)
arr[left], arr[right] = arr[right], arr[left] # ② 在 list 上操作
return ''.join(arr) # ③ list → str 还原
反转、替换字符、两两交换——全是这个流程。
join的写法是分隔符.join(列表),空串''就是直接拼接。
7.3 双指针原地反转
left, right = 0, len(arr) - 1
while left < right: # 注意 < 不是 >,重合位置不用交换
arr[left], arr[right] = arr[right], arr[left] # Python 一行交换,不需要 tmp
left += 1
right -= 1
7.4 步长 for 分段处理(替代手动 while + 偏移量)
for start in range(0, n, k): # 每 k 个一段,range 自动算步长
chunk = s[start:start + k] # 切片自动处理尾部不足
不要写
while pos < n: ... pos += k——手动维护偏移量容易区间算错。range(start, stop, step)的步长参数天然是做分段遍历的。
更多推荐



所有评论(0)