python基础语法命令(C程序员刷leetcode)

平时编码都用 C,但 C 刷算法效率太低(手写哈希/手动内存管理/一个反转字符串要写 5 行)。Python 把该省的事全省了。本文目标是:有 C 基础 + 零 Python 经验 → 读完能直接上 LeetCode 做题

算法基础练习总结入口:我的算法地图

文章目录

    • [一、背景:为什么要从 C 切换到 Python 刷题](#一、背景:为什么要从 C 切换到 Python 刷题)
      • [1. C vs Python 刷题差异总览---一张表看清省了什么、代价是什么](#1. C vs Python 刷题差异总览---一张表看清省了什么、代价是什么)
      • [2. Python 刷题的核心哲学---代码是解法的直译,不是在"编程"](#2. Python 刷题的核心哲学---代码是解法的直译,不是在"编程")
      • [3. 刷题需要的 Python 工具箱速览---一句话记住全景图](#3. 刷题需要的 Python 工具箱速览---一句话记住全景图)
    • [二、基础语法:C 程序员最关心的差异---数据类型、控制流、函数](#二、基础语法:C 程序员最关心的差异---数据类型、控制流、函数)
      • [1. 基础数据类型---int / float / bool / None,四种就够](#1. 基础数据类型---int / float / bool / None,四种就够)
      • [2. 数字运算---/ 总是浮点结果,// 才是整除,** 是乘方](#2. 数字运算---/ 总是浮点结果,// 才是整除,** 是乘方)
      • [3. 控制流---and/or/not 替代 &&/||/!,缩进替代 {},for 只有 range 一种写法](#3. 控制流---and/or/not 替代 &&/||/!,缩进替代 {},for 只有 range 一种写法)
      • [4. 函数---不声明类型 + 多返回值](#4. 函数---不声明类型 + 多返回值)
    • [三、输入输出---极简但 ACM 模式要知道几招](#三、输入输出---极简但 ACM 模式要知道几招)
      • [1. C vs Python 输入输出对照---scanf/printf → input/print,一张表覆盖语法迁移](#1. C vs Python 输入输出对照---scanf/printf → input/print,一张表覆盖语法迁移)
      • [2. 基础输入 `input()`---读一行字符串,剩下的自己拆](#2. 基础输入 input()---读一行字符串,剩下的自己拆)
      • [3. 基础输出 `print()`---默认换行,用参数调行为](#3. 基础输出 print()---默认换行,用参数调行为)
      • [4. ACM 四种输入模式---读到 EOF / 先读组数 / 不定长 / 混合输入,四段模板复制即用](#4. ACM 四种输入模式---读到 EOF / 先读组数 / 不定长 / 混合输入,四段模板复制即用)
    • [四、基础容器---list / dict / str / set / tuple,搞懂这五个 = 算法题不卡壳](#四、基础容器---list / dict / str / set / tuple,搞懂这五个 = 算法题不卡壳)
      • [1. list------万能动态数组,malloc+realloc 换成一个 \[\]](#1. list——万能动态数组,malloc+realloc 换成一个 [])
      • [2. dict------哈希表,手写哈希函数 → {} 直接用](#2. dict——哈希表,手写哈希函数 → {} 直接用)
      • [3. str------不可变但切片是杀手级武器](#3. str——不可变但切片是杀手级武器)
      • [4. set------去重和 O(1) 查找,比 C 的 bool 数组更通用](#4. set——去重和 O(1) 查找,比 C 的 bool 数组更通用)
      • [5. tuple------不可变轻量序列,pair<int,int> 的 Python 版](#5. tuple——不可变轻量序列,pair<int,int> 的 Python 版)
    • [五、专用工具---deque / heapq / bisect,特定场景用对工具](#五、专用工具---deque / heapq / bisect,特定场景用对工具)
      • [1. deque------双端队列,BFS 标配,list.pop(0) 的 O(1) 替代](#1. deque——双端队列,BFS 标配,list.pop(0) 的 O(1) 替代)
      • [2. heapq------堆,TopK 三行搞定,但只有小顶堆](#2. heapq——堆,TopK 三行搞定,但只有小顶堆)
      • [3. bisect------二分查找直接用,不用手写 left/right/mid 边界](#3. bisect——二分查找直接用,不用手写 left/right/mid 边界)
    • [六、C → Python 避坑指南---最容易踩的 8 个坑](#六、C → Python 避坑指南---最容易踩的 8 个坑)

一、背景:为什么要从 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% 出现的元素。不讲面向对象、不讲装饰器原理、不讲异步------刷题用不上。

text 复制代码
基础语法层    list → dict → str → set → tuple     ← 第四章 基础容器
专用工具层    deque → heapq → bisect                ← 第五章 专用工具
辅助技能层    input() / print() / f-string          ← 第三章 输入输出

"改造" C 程序员的习惯,不是学新语法,是忘记旧语法------忘掉 malloc、忘掉 strcpy、忘掉手写哈希。Python 的世界里,这些东西根本不存在。


二、基础语法:C 程序员最关心的差异---数据类型、控制流、函数

只用对照表 + 最简代码示例,不给完整语法教程。目标:3 分钟能看懂 Python 代码。

1. 基础数据类型---int / float / bool / None,四种就够

变量无需声明,类型跟着值走 。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

类型转换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) 绝对值,round(x) 四舍五入,pow(x, y) 等效 x**ymax(a, b) / min(a, b) 取大小------不需要 if (a > b)

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 弱,名字起好是关键。

② 多返回值------一行解包

python 复制代码
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 麻烦一步,但换来的是完全的解析控制权------想按什么规则拆就按什么规则拆。

python 复制代码
# 最高频写法------两个整数
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

python 复制代码
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 的 printff"{变量:格式}"{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) 一行里既有数字又有字符串
python 复制代码
# 模板 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 十行
python 复制代码
# 初始化
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,...]

# 访问------负索引是 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 循环 + 条件判断 + 变换操作,压成一行:

python 复制代码
# C: 取数组里所有偶数,平方,放到新数组
#    需要 malloc + for + if + 赋值,至少 6 行
# Python:
squares = [x*x for x in arr if x % 2 == 0]

二维列表------避坑

python 复制代码
# ✅ 正确写法------每行独立
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 里写动态数组:malloc n 个 → 满了 realloc 2n → 复制 → 释放旧空间。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(): 一行
python 复制代码
# 初始化
d = {}                            # 空字典
d = {"a": 1, "b": 2}             # 字面量

# 增删查
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------一行频次统计

python 复制代码
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 存在吗"的判断

python 复制代码
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] 革命性
python 复制代码
s = "Hello World"

# 切片------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 不在乎值多大、什么类型。

python 复制代码
s = {1, 2, 3}           # 字面量初始化
s = set()               # 空集合(注意 {} 是 dict 不是 set)

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 版

tuplelist 长得一样,唯一的区别------不可变 。创建之后不能增、不能删、不能改元素。这恰好让它能做 dict 的 key(list 不行------因为可变就不能哈希)。C 里最像它的是 pair<int,int>,但 tuple 可以有任意多个元素。

python 复制代码
t = (1, 2)              # 创建
t = 1, 2                # 括号可省略
a, b = t                # 解包:a=1, b=2
a, b = b, a             # 一行交换两个变量的值

# 为什么需要 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,否则数据量大直接超时。

python 复制代码
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)。

python 复制代码
# 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 默认大顶),需要大顶堆时------元素取个负就行。

python 复制代码
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 只有小顶堆,大顶堆 = 取负

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 把边界问题全封在库内,你只负责传数组和目标值。

python 复制代码
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 避坑指南---最容易踩的 8 个坑

# C 习惯 Python 实际行为 正确写法
1 字符串不可变 s[0] = 'x' 报错! str 不可原地改 s = 'x' + s[1:]
2 = 是引用 = 拷贝值 a = b 后改 a[0]b[0] 也变 a = b[:] 浅拷贝
3 默认参数陷阱 --- def f(arr=[]) 所有调用共享同一 list def f(arr=None):
4 for 内改循环变量 for(i=0;i<n;i++) i=10 生效 for i in range(n): i=10 无效 改用 while
5 负索引不报错 越界 = 段错误 arr[-1] 静默取最后一个 检查索引范围
6 整数除法 3/2 = 1(截断) 3/2 = 1.5(浮点)/ 3//2 = 1(整除) 整数除用 //
7 * 复制二维列表 --- [[0]*3]*3 三行指向同一 list [[0]*3 for _ in range(3)]
8 循环中删元素 --- for x in arr: arr.remove(x) 跳元素 [x for x in arr if cond]

"这些坑的共同特征------C 程序员的肌肉记忆在 Python 中不起作用,甚至反向作用。提前知道比 debug 时发现高效得多。"

相关推荐
geovindu1 小时前
python: Breadth First Search Algorithm and Depth First Search Algorithm
开发语言·后端·python·算法·搜索算法
程序员果子1 小时前
CrewAI :当 Agent 学会团队协作
人工智能·git·python·多智能体·agent框架
玉鸯1 小时前
向量检索不是记忆:Agent 记忆的三层进化
python·agent
大模型码小白1 小时前
Java 部署:Jenkins Pipeline 构建 Java 项目(自动化)
java·人工智能·python·机器学习
笨笨饿2 小时前
#102_Codex无在VSCold无法打开
java·c语言·数据库·笔记
paeamecium2 小时前
【PAT甲级真题】- Kuchiguse (20)
数据结构·c++·python·算法·pat考试·pat
青山木2 小时前
Hot 100 --- 全排列
java·数据结构·算法·leetcode·深度优先
满怀冰雪2 小时前
09-使用 paddle.nn 构建第一个多层感知机
python·深度学习·神经网络·paddle
小大宇3 小时前
python sqlalchemy 案例
开发语言·python