在算法题目中,处理数组(List)、字符串、矩阵等可迭代对象时,“同时获取索引和元素值” 是高频需求 —— 比如找目标元素的位置、双指针遍历标记、矩阵行列定位等。Python 内置的 enumerate 函数正是用来解决这类问题的。
enumerate 直译是 “枚举”,是 Python 内置函数,核心作用是遍历可迭代对象时,同时返回元素的 “索引” 和 “值”。它底层由 C 实现,执行效率略高于手动维护索引的写法,是刷题时的首选。
|
1 |
enumerate(iterable, start=0) |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# 传统写法 nums = [1, 3, 5, 7] i = 0 for val in nums: print(f"索引 {i}:值 {val}") i += 1 # enumerate 写法 for idx, val in enumerate(nums): print(f"索引 {idx}:值 {val}") # 自定义索引起始值(比如题目要求索引从 1 开始) for idx, val in enumerate(nums, start=1): print(f"索引 {idx}:值 {val}") |
题目原文:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。假设每种输入只会对应一个答案,数组中同一个元素在答案里不能重复出现,可按任意顺序返回答案。
传统暴力解法:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Solution(object): def twoSum(self, nums, target): """ :type nums: List[int] :type target: int :rtype: List[int] """ n=len(nums) for i in range(n): for j in range(i+1,n): if target == nums[i]+nums[j]: return[i,j] |
传统暴力解法需要两层循环(时间复杂度 O(n2)),而哈希表可以将「查找另一个数」的操作从 O(n) 降到 O(1),整体时间复杂度优化为 O(n),空间复杂度 O(n)(存储哈希表):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
from typing import List # 导入List类型注解,用于指定参数和返回值类型 class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: """ 给定一个整数数组 nums 和一个整数目标值 target, 请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,且同一个元素不能使用两遍。 你可以按任意顺序返回答案。 :param nums: 整数数组,存储待查找的数字 :param target: 目标和,需要找到两个数相加等于该值 :return: 包含两个数下标List[int],顺序无要求 """ # 初始化一个空字典(哈希表),用于存储「数字: 下标」的映射 # 作用:将遍历过的数字和其下标记录下来,后续可以O(1)时间查询是否存在目标差值 hashtable = dict() # 遍历数组,enumerate同时返回下标i和对应数字num for i, num in enumerate(nums): # 计算目标差值:target - 当前数字 = 需要找的另一个数 complement = target - num # 检查哈希表中是否已经存在这个差值 # 如果存在,说明之前遍历过的某个数字和当前数字相加等于target if complement in hashtable: # 返回「差值对应的下标」和「当前数字的下标」,即为答案 return [hashtable[complement], i] # 如果不存在,将当前数字和其下标存入哈希表,供后续遍历查询 # 注意:先检查再存入,避免同一个元素被重复使用(比如num=3, target=6时,不会匹配到自己) hashtable[nums[i]] = i # 题目保证有且仅有一个答案,理论上不会执行到这里,返回空列表作为兜底 return [] |
核心场景:需要 “值 + 下标” 绑定且下标是答案的一部分
enumerate的核心作用:遍历数组时,既要获取元素值(计算补数target-num),又要记录该值对应的下标(最终需要返回下标作为答案)。
适用细节:
题目原文:
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。要求原地操作,不拷贝额外数组,尽量减少操作次数。
非enumerate解法:
|
1 2 3 4 5 6 7 8 9 |
class Solution(object): def moveZeroes(self, nums): n=len(nums) left = right = 0 while right<n: if nums[right]!=0: nums[left],nums[right]=nums[right],nums[left] left+=1 right+=1 |
enumerate解法:
|
1 2 3 4 5 6 7 8 |
class Solution(object): def moveZeroes(self, nums): slow = 0 # 慢指针:记录非零元素应存放的位置,初始为0 # enumerate的fast_idx作为快指针,遍历数组筛选非零元素 for fast_idx, val in enumerate(nums): if val != 0: # 遇到非零元素,与慢指针位置交换并后移慢指针 nums[slow], nums[fast_idx] = nums[fast_idx], nums[slow] slow += 1 |
核心场景:双指针遍历,快指针需同时获取 “索引 + 值” 做筛选
enumerate的核心作用:快指针遍历数组时,既要通过val判断 “是否为 0”(筛选非零元素),又要通过fast_idx(enumerate 的索引)与慢指针交换元素(原地修改数组)。
适用细节:
题目原文
给定两个字符串 s 和 p,找到 s 中所有 p 的 字母异位词 的子串,返回这些子串的起始索引。字母异位词指由相同字母重排列形成的字符串(包括相同字符串),不考虑答案输出的顺序。
非enumerate写法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Solution(object): def findAnagrams(self, s, p): """ :type s: str # 原字符串 :type p: str # 目标异位词的基准字符串 :rtype: List[int] # 返回所有异位词在s中的起始索引 """ # 初始化结果列表 result = [] # 获取p和s的长度 len_p, len_s = len(p), len(s) # 边界条件:如果p比s长,不可能有异位词,直接返回空列表 if len_p > len_s: return result # 初始化两个数组,统计26个小写字母的出现次数(ASCII码映射) # ord('a') = 97,所以用 ord(c) - ord('a') 得到0-25的索引 count_p = [0] * 26 count_window = [0] * 26 # 第一步:统计p的字符计数,同时初始化滑动窗口的初始状态(前len_p个字符) for i in range(len_p): # 统计p的字符 count_p[ord(p[i]) - ord('a')] += 1 # 统计s前len_p个字符(初始窗口) count_window[ord(s[i]) - ord('a')] += 1 # 检查初始窗口是否匹配 if count_p == count_window: result.append(0) # 第二步:滑动窗口遍历s的剩余部分(从len_p到len_s-1) # 窗口起始索引从1开始,结束索引从len_p开始 for right in range(len_p, len_s): # 移出窗口左侧的字符(窗口起始索引是 right - len_p) left_char = s[right - len_p] count_window[ord(left_char) - ord('a')] -= 1 # 移入窗口右侧的新字符 right_char = s[right] count_window[ord(right_char) - ord('a')] += 1 # 检查当前窗口是否匹配p的字符计数 if count_p == count_window: # 记录当前窗口的起始索引 result.append(right - len_p + 1) return result |
enumerate写法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class Solution(object): def findAnagrams(self, s, p): """ :type s: str :type p: str :rtype: List[int] """ result = [] len_p, len_s = len(p), len(s) # 边界条件:p比s长直接返回空 if len_p > len_s: return result # 初始化26字母计数数组 count_p = [0] * 26 count_window = [0] * 26 # 第一步:统计p的字符,同时初始化窗口前len_p个字符(用enumerate遍历) # 遍历p的字符,统计计数 for idx, char in enumerate(p): count_p[ord(char) - ord('a')] += 1 # 遍历s前len_p个字符,初始化窗口(用enumerate更清晰) for idx, char in enumerate(s[:len_p]): count_window[ord(char) - ord('a')] += 1 # 检查初始窗口是否匹配 if count_p == count_window: result.append(0) # 第二步:滑动窗口(用enumerate遍历s中从len_p开始的部分) # 遍历的是s[len_p:],enumerate的start参数指定索引从len_p开始(对应原s的索引) for right_idx, right_char in enumerate(s[len_p:], start=len_p): # 计算要移出窗口的左侧字符的索引 left_idx = right_idx - len_p left_char = s[left_idx] # 移出左侧字符:计数-1 count_window[ord(left_char) - ord('a')] -= 1 # 移入右侧新字符:计数+1 count_window[ord(right_char) - ord('a')] += 1 # 检查当前窗口是否匹配,匹配则记录起始索引(left_idx + 1) if count_p == count_window: result.append(left_idx + 1) return result |
核心场景:滑动窗口遍历,需索引计算窗口边界 + 值维护窗口状态
enumerate的核心作用:遍历字符串时,既要通过char(值)更新窗口字符计数,又要通过idx(索引)计算窗口左边界(idx - p_len + 1)、判断窗口长度是否达标。
适用细节:
题目原文
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用 原地 算法。
进阶:能否使用 O (1) 额外空间?
非enumerate解法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Solution(object): def setZeroes(self, matrix): """ :type matrix: List[List[int]] :rtype: None Do not return anything, modify matrix in-place instead. """ # 初始化两个集合,分别存储需要置零的行和列 rows = set() cols = set() # 第一步:遍历矩阵,标记需要置零的行和列 m = len(matrix) # 矩阵的行数 if m == 0: return n = len(matrix[0]) # 矩阵的列数 for i in range(m): for j in range(n): if matrix[i][j] == 0: rows.add(i) cols.add(j) # 第二步:根据标记的行和列,将对应位置置零 for i in range(m): for j in range(n): if i in rows or j in cols: matrix[i][j] = 0 |
enumerate解法:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Solution(object): def setZeroes(self, matrix): """ :type matrix: List[List[int]] :rtype: None Do not return anything, modify matrix in-place instead. """ # 初始化两个集合,分别存储需要置零的行和列 rows = set() cols = set() # 第一步:遍历矩阵,用enumerate获取行索引+行数据,标记需要置零的行和列 # 外层enumerate:i是行索引,row是当前行的所有元素 for i, row in enumerate(matrix): # 内层enumerate:j是列索引,val是当前位置的元素值 for j, val in enumerate(row): if val == 0: rows.add(i) # 标记需要置零的行 cols.add(j) # 标记需要置零的列 # 第二步:再次遍历矩阵,根据标记置零(同样用enumerate) for i, row in enumerate(matrix): for j, _ in enumerate(row): # 只需要列索引j,元素值用_占位即可 if i in rows or j in cols: matrix[i][j] = 0 |
刷题中常需要快速查找元素的位置,用 enumerate + 字典推导式可一键生成映射表:
|
1 2 3 4 5 |
# 生成“元素值: 作为字典的“键”(适用于无重复元素的数组) nums = [2, 7, 11, 15] # 字典推导式 + enumerate:键是元素值,值是对应索引 val2idx = {val: idx for idx, val in enumerate(nums)} print(val2idx[7]) # 输出:1(快速找到7的索引) |
适用场景:Hot100 两数之和(快速通过值找索引,时间复杂度从 O (n²) 降为 O (n))。
enumerate 返回的是迭代器,只能遍历一次;若需多次使用,可转为列表:
|
1 2 3 4 |
nums = [10, 20, 30] # 将枚举对象转为列表,方便查看所有(索引,值)对 enum_list = list(enumerate(nums)) print(enum_list) # 输出:[(0, 10), (1, 20), (2, 30)] |
部分题目要求索引从 1 开始(如 “第 k 个元素”),只需设置 start=1:
|
1 2 3 4 |
nums = [5, 3, 8] # start=1 让索引从1开始,符合题目“第k个元素”的表述习惯 for idx, val in enumerate(nums, start=1): print(f"第 {idx} 个元素:{val}") # 输出:第1个元素:5,第2个元素:3... |
|
1 2 3 4 |
nums = [1, 2, 3, 4] # 遍历切片 nums[2:](元素是3、4),enumerate 的索引从0开始,而非原数组的2 for idx, val in enumerate(nums[2:]): print(idx, val) # 输出:0 3;1 4 |