LeetCode Hot100数据结构背景知识之列表(List)Python2026新版

一、List的本质:Python中的"动态数组"

首先要明确一个关键认知:Python中的List 并非传统意义上的"链表"(ListedNode) ,而是一种动态数组(Dynamic Array)。这一点与C++的vector、Java的ArrayList本质一致。

1.1 底层逻辑与特性

传统的静态数组(如C语言的数组)在初始化时必须指定固定大小,且后续无法灵活扩容/缩容,一旦元素数量超过数组长度,就需要手动申请新的内存空间、拷贝原有元素,操作繁琐且效率低下。而Python的List则解决了这一痛点,其底层实现逻辑如下:

  • List会预先申请一块连续的内存空间(初始容量通常较小,如4个元素),用于存储元素;

  • 当元素数量达到当前容量上限时,List会自动扩容(通常扩容为原容量的1.5倍或2倍,具体取决于Python版本),申请一块更大的连续内存,将原有元素拷贝到新空间,再释放旧空间;

  • 当元素数量大幅减少时,List也会自动缩容(部分版本支持),避免内存浪费;

  • 所有元素按顺序存储在连续内存中,因此可以通过索引(index)直接访问元素,这也是"数组"的核心优势。

这里需要区分两个易混淆的概念:容量(capacity)长度(length)

  • 长度(len(list)):当前List中实际存储的元素个数,随时变化;

  • 容量(capacity):底层连续内存空间能容纳的最大元素个数,由Python自动管理,用户无法直接获取或修改。

1.2 与链表(Linked List)的核心区别

很多初学者会将List与链表混淆,但二者的操作效率差异极大,直接决定了刷题时的算法选择(比如LeetCode中"删除链表的倒数第N个节点"不能用List模拟,否则效率过低)。具体区别如下表:

操作 Python List(动态数组) 链表(Linked List)
按索引访问(查) O(1)(直接定位内存地址) O(n)(需从头遍历到目标节点)
头部插入/删除(增/删) O(n)(需移动所有后续元素) O(1)(只需修改头节点指针)
尾部插入/删除(增/删) O(1)(无需移动元素,除非扩容) O(n)(需遍历到尾节点,双向链表除外)
中间插入/删除(增/删) O(n)(需移动插入/删除位置后的所有元素) O(n)(需遍历到目标位置)
内存占用 可能有冗余(扩容后未用完的空间) 无冗余(每个节点只存储自身数据和指针)

总结:LeetCode中涉及"频繁按索引访问、尾部增删"的题目,优先用Python List;涉及"频繁头部增删、中间插入删除且数据量较大"的题目,需考虑用链表(Python无内置链表,可自定义或用collections.deque模拟双向链表)。

二、Python List的核心操作

2.1 初始化与赋值

刷题中最常用的3种初始化方式,根据场景选择:

python 复制代码
# 1. 空列表(最常用,如存储结果、临时变量)
lst = []

# 2. 初始化时传入元素(已知初始数据,如题目给出的数组)
lst = [1, 2, 3, 4, 5]

# 3. 初始化指定长度、默认值(适用于需要固定长度的场景,如动态规划的dp数组)
lst = [0] * 5  # 结果:[0, 0, 0, 0, 0]
lst = [None] * 3  # 结果:[None, None, None]

【拓展】

列表推导式(List Comprehension) 适合基于已有列表生成新列表的场景。

1. 基础列表推导式(最常用)

核心语法:new_lst = [表达式 for 元素 in 可迭代对象]时间复杂度与普通遍历一致(O (n))。

python 复制代码
# 场景1:基于已有列表生成新列表(如你提到的平方操作)
a = [1, 2, 3, 4]
b = [i*i for i in a]  # 结果:[1, 4, 9, 16]

# 场景2:生成指定范围的列表(替代range+append)
# 生成1-10的奇数列表(Hot100中"两数之和""三数之和"常用来构造测试用例)
odd_nums = [x for x in range(1, 11) if x % 2 != 0]  # 结果:[1, 3, 5, 7, 9]

# 场景3:过滤列表元素(如LeetCode"移除元素"预处理)
nums = [3, 2, 2, 3]
target = 3
filtered_nums = [num for num in nums if num != target]  # 结果:[2, 2]
2. 带条件判断的列表推导式

核心语法:new_lst = [表达式1 if 条件 else 表达式2 for 元素 in 可迭代对象]适用于需要 "按需生成元素" 的场景,比如 LeetCode 中 "将数组中的偶数翻倍,奇数不变"。

python 复制代码
# 场景:LeetCode"调整数组元素"类题目
nums = [1, 2, 3, 4, 5]
# 偶数乘2,奇数保持不变
adjusted_nums = [num*2 if num % 2 == 0 else num for num in nums]  # 结果:[1, 4, 3, 8, 5]

# 场景:生成n阶单位矩阵(矩阵乘法/线性代数相关题目常用)
n = 4
# 生成4x4单位矩阵,对角线元素(i==j)为1,其余为0
matrix = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
# 结果:
# [[1, 0, 0, 0],
#  [0, 1, 0, 0],
#  [0, 0, 1, 0],
#  [0, 0, 0, 1]]

2.2 核心增删改查操作

(1)查:按索引访问、遍历
  • 按索引访问:lst[i],索引从0开始,支持负索引(lst[-1] 表示最后一个元素),时间复杂度O(1);

  • 遍历元素:两种常用方式,时间复杂度均为O(n)

    python 复制代码
    # 方式1:直接遍历元素(最常用,无需关注索引)
    for num in lst:
        print(num)
    
    # 方式2:遍历索引+元素(需要索引时用,如双指针题目)
    for i, num in enumerate(lst):
        print(i, num)

    如果不了解enumerate()函数的使用,可以查看我的另一篇博客:

    LeetCode Hot100 中 enumerate 函数的妙用(2026.2月版)

  • 查找元素位置:lst.index(target),返回第一个匹配元素的索引,无匹配元素则报错,时间复杂度O(n);刷题时常用 target in lst 判断元素是否存在(时间复杂度O(n))。

(2)改:修改指定索引元素

lst[index] = new_val,直接通过索引修改,时间复杂度O(1)「高频」,例如:

python 复制代码
lst = [1, 2, 3]
lst[1] = 4  # 结果:[1, 4, 3]
(3)增:添加元素
  • 尾部添加:lst.append(val),时间复杂度O(1)(除非扩容,扩容时为O(n),但平均复杂度仍为O(1))「高频」;

  • 指定位置插入:lst.insert(index, val),时间复杂度O(n)(需移动后续元素),刷题时尽量避免频繁使用(会导致效率低下);

  • 合并两个列表:lst1.extend(lst2)(将lst2的元素添加到lst1尾部),时间复杂度O(k)(k为lst2的长度),优于 lst1 + lst2(会创建新列表,时间复杂度O(n+k))。

(4)删:删除元素
  • 按索引删除:del lst[index],时间复杂度O(n)(需移动后续元素)「高频」;

  • 删除最后一个元素:lst.pop(),时间复杂度O(1)「高频」;按索引删除:lst.pop(index),时间复杂度O(n);

  • 按元素删除:lst.remove(val),删除第一个匹配的元素,无匹配元素则报错,时间复杂度O(n);

  • 清空列表:lst.clear(),时间复杂度O(n)(释放所有元素)。

2.3 常用进阶操作

  • 切片(Slice):lst[start:end:step],截取列表的一部分,返回新列表,时间复杂度O(k)(k为切片长度)。

python 复制代码
lst = [1, 2, 3, 4, 5]
lst[1:3]  # 从索引1到2(不包含3):[2, 3]
lst[::2]  # 步长为2,取所有奇数索引:[1, 3, 5]
lst[::-1]  # 步长为-1,反转列表:[5, 4, 3, 2, 1]
  • ⚠️ 注意:切片会创建新列表,若列表过大,频繁切片会占用额外内存,可考虑用双指针替代。

  • 排序:lst.sort(),原地排序(修改原列表),时间复杂度O(nlogn);sorted(lst),返回新的排序后列表,原列表不变「高频」。刷题时常用 lst.sort(key=lambda x: x) 自定义排序规则(如按元素绝对值排序、按二维数组的第二列排序)。

  • 去重:list(set(lst)),先将列表转为集合(自动去重),再转回列表,时间复杂度O(n),但会打乱原有的元素顺序;若需保留原顺序,可结合字典(Python 3.7+ 字典有序):

python 复制代码
 lst = [3, 1, 2, 1, 3, 4]
# 转集合去重 → 无序
s = set(lst)  # 结果可能是 {1,2,3,4}(顺序不固定)
# 转回列表 → 顺序被打乱
new_lst = list(s)
print(new_lst)  # 可能输出 [1,2,3,4] 或 [2,1,4,3] 等,和原顺序不一致


lst = [2, 1, 2, 3, 1] lst_unique = list(dict.fromkeys(lst)) 
# 保留原顺序:[2, 1, 3]
  • 统计元素出现次数:lst.count(val),时间复杂度O(n),适用于需要统计频率的题目

总结:

分类 函数 / 方法 核心作用 简单示例
append(x) 在列表末尾添加单个元素 x(直接修改原列表) lst = [1,2]; lst.append(3)[1,2,3]
extend(iter) 把可迭代对象(如列表、字符串)的元素逐个添加到列表末尾 lst = [1,2]; lst.extend([3,4])[1,2,3,4]
insert(idx, x) 在指定索引idx位置插入元素 x(后面元素后移) lst = [1,3]; lst.insert(1,2)[1,2,3]
remove(x) 删除列表中第一个出现的元素 x(无该元素则报错) lst = [1,2,2,3]; lst.remove(2)[1,2,3]
pop([idx]) 删除指定索引idx的元素(默认删最后一个),返回被删除的元素 lst = [1,2,3]; lst.pop(1) → 返回2,列表变为[1,3]
clear() 清空列表所有元素(原列表变为空) lst = [1,2]; lst.clear()[]
del lst[idx] (关键字)删除指定索引 / 切片的元素(直接修改原列表) lst = [1,2,3]; del lst[1][1,3]
lst[idx] = x 直接修改指定索引位置的元素值 lst = [1,2]; lst[1] = 3[1,3]
reverse() 反转列表元素顺序(直接修改原列表) lst = [1,2,3]; lst.reverse()[3,2,1]
sort(key=None, reverse=False) 对列表排序(默认升序,reverse=True降序;直接修改原列表) lst = [3,1,2]; lst.sort()[1,2,3]lst.sort(reverse=True)[3,2,1]
index(x, [start, end]) 查找元素 x 在列表中第一个出现的索引(指定 start/end 限定范围,无则报错) lst = [1,2,3]; lst.index(2)1
count(x) 统计元素 x 在列表中出现的次数 lst = [1,2,2,3]; lst.count(2)2
len(lst) (内置函数)返回列表元素个数 lst = [1,2,3]; len(lst)3
x in lst (运算符)判断元素 x 是否在列表中,返回布尔值 1 in [1,2,3]True4 in [1,2,3]False
复制 copy() 浅拷贝列表(返回新列表,原列表修改不影响新列表) lst = [1,2]; new_lst = lst.copy()new_lst = [1,2]
lst[:] 切片方式浅拷贝(和 copy () 等价) lst = [1,2]; new_lst = lst[:]new_lst = [1,2]
其他 copy.deepcopy(lst) (需导入 copy 模块)深拷贝(嵌套列表也完全复制) import copy; lst = [[1],2]; new_lst = copy.deepcopy(lst)
sorted(lst) (内置函数)返回排序后的新列表(原列表不变) lst = [3,1,2]; sorted(lst)[1,2,3](lst 仍为[3,1,2]
enumerate(lst) (内置函数)返回迭代器,包含 (索引,元素) 对 for idx, val in enumerate([1,2]): print(idx, val) → 0 1 / 1 2
max(lst)/min(lst) (内置函数)返回列表中最大 / 最小值(元素需可比较) max([1,2,3])3min([1,2,3])1
sum(lst) (内置函数)返回列表所有元素的和(元素需为数字) sum([1,2,3])6

修改原列表 vs 返回新列表

  • 直接修改原列表的方法:append()extend()insert()remove()pop()reverse()sort()
  • 返回新列表 / 值的:copy()sorted()index()count()len()max()min()sum()

三、注意事项:

在做List相关的Hot100题目时,很多人会因忽略细节导致代码超时或出错:

  1. 避免频繁在List头部插入/删除元素:头部操作时间复杂度为O(n),若数据量较大(如n>10^4),频繁操作会导致超时;

  2. 慎用lst + lst2合并列表:每次合并都会创建新列表,若需多次合并,优先用extend()

lst + lst2 的底层逻辑不是 "在原列表上追加",而是:

  1. 申请一块新的内存空间 ,大小 = len(lst) + len(lst2)
  2. lst 的所有元素复制到新空间;
  3. 再把 lst2 的所有元素复制到新空间;
  4. 返回这个新列表,原列表 lst 本身不会改变。
  5. 如果写 lst= lst+ lst2,则相当于lst 这个变量名重新指向新列表,原列表的内存会被垃圾回收。

lst.extend(lst2) 的逻辑是:

  1. 直接在 lst 原有的内存空间后 "扩容"(按需申请少量新内存,而非全部);

  2. lst2 的元素逐个追加lst 末尾,不复制 lst 原有元素

  3. 直接修改原列表,无新列表创建。

  4. 注意List的可变特性:函数中修改传入的List,会直接修改原列表(因为List是引用类型),若需保留原列表,需传入切片lst.copy()lst[:]

  5. 排序后索引变化:对List排序后,元素的原始索引会被打乱,若题目需要保留原始索引(如"两数之和"),需提前存储索引与元素的对应关系;

  6. 边界条件处理:刷题时务必考虑List为空(len(lst) == 0)、List只有一个元素、索引越界等边界情况,这是避免WA(Wrong Answer)的关键。

相关推荐
仟濹2 小时前
算法打卡 day1 (2026-02-06 周四) | 算法: DFS | 1_卡码网98 可达路径 | 2_力扣797_所有可能的路径
算法·leetcode·深度优先
历程里程碑2 小时前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法
DeeplyMind2 小时前
第七章:数据结构大比拼
数据结构·计算机科学·少儿编程·少儿科技读物
元亓亓亓2 小时前
考研408--数据结构--day8--遍历序列&线索二叉树
数据结构·考研·408·线索二叉树
xiaoxue..2 小时前
合并两个升序链表 与 合并k个升序链表
java·javascript·数据结构·链表·面试
驭渊的小故事3 小时前
简单模板笔记
数据结构·笔记·算法
YuTaoShao3 小时前
【LeetCode 每日一题】1653. 使字符串平衡的最少删除次数——(解法一)前后缀分解
算法·leetcode·职场和发展
VT.馒头3 小时前
【力扣】2727. 判断对象是否为空
javascript·数据结构·算法·leetcode·职场和发展
历程里程碑5 小时前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法