跳到主要内容
LeetCode 热题 100 Python3 算法题解:哈希、双指针与滑动窗口 | 极客日志
Python 算法
LeetCode 热题 100 Python3 算法题解:哈希、双指针与滑动窗口 LeetCode 热题 100 Python3 算法题解,涵盖哈希表、双指针、滑动窗口及子数组等核心数据结构与算法技巧。内容包含两数之和、字母异位词分组、最长连续序列、移动零、盛最多水的容器、三数之和、接雨水、无重复字符的最长子串、找到字符串中所有字母异位词以及和为 K 的子数组等经典题目。提供详细解题思路、Python 代码实现及复杂度分析,适合算法初学者进阶学习。
PentesterX 发布于 2026/3/16 更新于 2026/4/26 15 浏览哈希
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入: nums = [2,7,11,15], target = 9 输出: [0,1] 解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入: nums = [3,2,4], target = 6 输出: [1,2]
示例 3:
输入: nums = [3,3], target = 6 输出: [0,1]
提示:
2 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
只会存在一个有效答案
class Solution :
def twoSum (self, nums: List [int ], target: int ) -> List [int ]:
for i in range (len (nums)):
for n in range (i+1 , len (nums)):
if target == nums[i] + nums[n]:
return [i, n]
该解法为暴力遍历。直接遍历整个列表,从第一个值开始,如果从当前位置到最后位置中有和当前位置值相加为目标值的即返回这两个位置。
给你一个字符串数组,请你将 组合在一起。可以按任意顺序返回结果列表。
字母异位词
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
在 strs 中没有字符串可以通过重新排列来形成 "bat"。
字符串 "nat" 和 "tan" 是字母异位词,因为它们可以重新排列以形成彼此。
字符串 "ate","eat" 和 "tea" 是字母异位词,因为它们可以重新排列以形成彼此。
1 <= strs.length <= 10^4
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
class Solution :
def groupAnagrams (self, strs: List [str ] ) -> List [List [str ]]:
d = defaultdict(list )
for s in strs:
sorted_s = '' .join(sorted (s))
d[sorted_s].append(s)
return list (d.values())
此题需使用哈希表优化。defaultdict(list) 访问不存在的键时会自动创建键并将值设为空列表,无需判断键是否存在。
sorted(s) 将字符串拆成字符并排序,得到 ['a', 'e', 't']。
''.join(...) 将字符列表拼接回字符串 "aet"。这是这组字母异位词的 唯一标识(钥匙) 。无论原字符串是 "eat"、"tea" 还是 "ate",它们排序后都会得到相同的 "aet"。
分组归位 (d[sorted_s].append(s))
通过生成的密钥 sorted_s 操作字典 d。如果是第一次遇到 "aet" 这个键,d 会自动创建并将其值初始化为空列表,然后立刻将原字符串添加进去。当处理到 "tea" 时,排序后密钥同样是 "aet",此时 d["aet"] 已存在,直接调用 append("tea")。
处理完所有字符串后,字典内容类似 {"aet": ["eat", "tea"], "ant": ["tan"]}。
输出结果 (return list(d.values()))
给定一个未排序的整数数组 nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
输入: nums = [100,4,200,1,3,2] 输出: 4 解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
输入: nums = [0,3,7,2,5,8,4,6,0,1] 输出: 9
输入: nums = [1,0,1,2] 输出: 3
0 <= nums.length <= 10^5
-10^9 <= nums[i] <= 10^9
class Solution :
def longestConsecutive (self, nums: List [int ] ) -> int :
st = set (nums)
ans = 0
for x in st:
if x - 1 in st:
continue
y = x + 1
while y in st:
y += 1
ans = max (ans, y - x)
return ans
初始思路为排序,但题目要求时间复杂度为 O(n),而排序为 O(nlogn),因此考虑哈希表。
首先把 nums 转为哈希集合,这样判断数字在 nums 里只需要 O(1)。然后遍历哈希集合,如果 x-1 在集合里,那么 x 可以直接跳过,因为不可能比从 x-1 更长了。最后设置 y 为 x+1,如果 y 在集合里,则 y 再加一,最后 ans 为现有最大 ans 和当前 y-x 中的最大值。
双指针 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
输入: nums = [0,1,0,3,12] 输出: [1,3,12,0,0]
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1
class Solution :
def moveZeroes (self, nums: List [int ] ) -> None :
left = right = 0
n = len (nums)
while right < n:
if nums[right] != 0 :
nums[left], nums[right] = nums[right], nums[left]
left += 1
right += 1
给定一个长度为 n 的整数数组 height。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i])。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
输入: [1,8,6,2,5,4,8,3,7] 输出: 49 解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
n == height.length
2 <= n <= 10^5
0 <= height[i] <= 10^4
class Solution :
def maxArea (self, height: List [int ] ) -> int :
left, right = 0 , len (height) - 1
maxar = 0
while left < right:
maxar = max (maxar, (right - left) * min (height[left], height[right]))
if height[left] < height[right]:
left += 1
else :
right -= 1
return maxar
第一反应是两个 for 循环,但时间复杂度过不去,所以用双指针。
首先在数组首尾设立双指针,此时最大面积等于指针指向位置中较小的数乘以指针之间的距离。
置为首尾,只能移动值较小的指针。
若移动较大值的指针,距离减小,较小值只可能不变或减小,面积一定减小。
反之,移动较小值的指针,面积则可能增大。
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时还满足 nums[i] + nums[j] + nums[k] == 0。请你返回所有和为 0 且不重复的三元组。
输入: nums = [-1,0,1,2,-1,-4] 输出: [[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0。不同的三元组是 [-1,0,1] 和 [-1,-1,2]。注意,输出的顺序和三元组的顺序并不重要。
输入: nums = [0,1,1] 输出: [] 解释: 唯一可能的三元组和不为 0。
输入: nums = [0,0,0] 输出: [[0,0,0]] 解释: 唯一可能的三元组和为 0。
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5
class Solution :
def threeSum (self, nums: List [int ] ) -> List [List [int ]]:
nums.sort()
res, k = [], 0
for k in range (0 , len (nums) - 2 ):
if nums[k] > 0 :
break
if k > 0 and nums[k] == nums[k - 1 ]:
continue
i, j = k + 1 , len (nums) - 1
while i < j:
s = nums[k] + nums[i] + nums[j]
if s < 0 :
i += 1
while i < j and nums[i] == nums[i - 1 ]:
i += 1
elif s > 0 :
j -= 1
while i < j and nums[j] == nums[j + 1 ]:
j -= 1
else :
res.append([nums[k], nums[i], nums[j]])
i += 1
j -= 1
while i < j and nums[i] == nums[i - 1 ]:
i += 1
while i < j and nums[j] == nums[j + 1 ]:
j -= 1
return res
首先固定指针 k 遍历数组,然后用双指针 i,j 分别从 k 相邻的右边的数和最右边的数一个向后一个向前去遍历所有右边的数,只要三个相加为零就放到新的数组里面去。
当 nums[k] > 0 时直接 break,因为此时三数之和不可能等于 0;
如果 k > 0 并且 nums[k] == nums[k - 1],此时可以直接跳过当前数,避免重复;
让 ij 分散在 k 右边数的两端,当 i < j 时,s 等于三个指针指向的三数之和,如果 s < 0 那么 i 往右移,并且跳过重复值,s > 0 则 j 往左移并且跳过重复值,如果 s = 0,那么把当前数值放入数组中,在 i 往右移 j 往左移的同时跳过重复值;
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
输入: height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出: 6 解释: 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
输入: height = [4,2,0,3,2,5] 输出: 9
n == height.length
1 <= n <= 2 * 10^4
0 <= height[i] <= 10^5
class Solution :
def trap (self, height: List [int ] ) -> int :
n = len (height)
res = 0
left, right = 0 , n - 1
leftmax, rightmax = height[left], height[right]
left += 1
right -= 1
while left <= right:
leftmax = max (leftmax, height[left])
rightmax = max (rightmax, height[right])
if leftmax < rightmax:
res += leftmax - height[left]
left += 1
else :
res += rightmax - height[right]
right -= 1
return res
该解法使用双指针。定义 left 和 right 分别在 height 数组的最左端和最右端,当前的指针遍历过的最大值置为 leftmax 和 rightmax。
因为当前最左端和最右端是不可能装水的,所以 left 右移,right 左移。
当 left <= right 时,更新 leftmax 和 rightmax。
此时,如果 leftmax < rightmax,说明左指针的右边一定有一个数值大于左指针左边的所有数值,决定当前列能装多少水的一定在左边(水桶的短板效应),所以当前列能装的水的容量就等于左边最大数值减去当前列的数值,然后 left 指针右移。
同理,如果 leftmax >= rightmax,那么当前列所能装的水等于右边最大数值减去当前列数值,right 指针左移。
滑动窗口 给定一个字符串 s,请你找出其中不含有重复字符的 最长 子串 的长度。
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个 *子序列,*不是子串。
0 <= s.length <= 5 * 10^4
s 由英文字母、数字、符号和空格组成
class Solution :
def lengthOfLongestSubstring (self, s: str ) -> int :
dic, res, i = {}, 0 , -1
for j in range (len (s)):
if s[j] in dic:
i = max (dic[s[j]], i)
dic[s[j]] = j
res = max (res, j - i)
return res
指针 j 遍历 s,哈希表 dic 统计字符 s[j] 最后一次出现的索引。左指针 i 初始值为 -1,也就是 j-i 就是当前不重复字符串的最大长度。
如果指针 j 当前指向的字母已经在哈希表里,那么更新 i 为哈希表中此字母的值与 i 之间的最大值。
在上述操作结束后再更新哈希表里当前字母出现的最后位置。
最大长度等于之前的最大长度与当下 j-i 也就是不重复字符串长度的最大值。
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
输入: s = "abab", p = "ab" 输出: [0,1,2] 解释: 起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。
1 <= s.length, p.length <= 3 * 10^4
s 和 p 仅包含小写字母
class Solution :
def findAnagrams (self, s: str , p: str ) -> List [int ]:
m, n, res = len (s), len (p), []
if n > m:
return res
s_cnt = [0 ] * 26
p_cnt = [0 ] * 26
for i in range (n):
s_cnt[ord (s[i]) - ord ('a' )] += 1
p_cnt[ord (p[i]) - ord ('a' )] += 1
if s_cnt == p_cnt:
res.append(0 )
for i in range (n, m):
s_cnt[ord (s[i - n]) - ord ('a' )] -= 1
s_cnt[ord (s[i]) - ord ('a' )] += 1
if s_cnt == p_cnt:
res.append(i - n + 1 )
return res
首先定义 m,n 为 s,p 的长度,res 记录子串的起始索引。
然后创建初始值为 0 的两个长度为 26 的数组,用来表示 26 个字母出现次数。
一个数组用来统计 p 中字母出现的次数,另一个统计滑动窗口里字母出现次数。数组相等就输出滑动窗口左端位置。
子串 给你一个整数数组 nums 和一个整数 k,请你统计并返回 该数组中和为 k 的子数组的个数 。
输入: nums = [1,1,1], k = 2 输出: 2
输入: nums = [1,2,3], k = 3 输出: 2
1 <= nums.length <= 2 * 10^4
-1000 <= nums[i] <= 1000
-10^7 <= k <= 10^7
class Solution :
def subarraySum (self, nums: List [int ], k: int ) -> int :
res = 0
n = len (nums)
for i in range (n):
for j in range (i, n):
if sum (nums[i:j+1 ]) == k:
res += 1
return res
class Solution :
def subarraySum (self, nums: List [int ], k: int ) -> int :
res = 0
n = len (nums)
presums = defaultdict(int )
presums[0 ] = 1
presum = 0
for i in range (n):
presum += nums[i]
res += presums[presum - k]
presums[presum] += 1
return res
首先创建默认值为 0 的哈希表(字典)。defaultdict 会在键不存在时返回指定的默认值(这里是 0),而不是引发 KeyError。
将 presums[0] 置为 1,也就是前缀和为 0 目前出现过一次。当前前缀和为 0。
遍历该数组,计算当前位置的前缀和。连续子数组的和为 k 的个数用 res 表示,此时 res 等于之前的 res 加上哈希表中当前前缀和减去 k 的位置的值,意思就是,比如 presum - k 这个前缀和如果出现了 4 次,那么 presum - (presum - k) = k 这个值就出现了四次,res+4。
然后把前缀和出现次数的哈希表中当前前缀和位置的值加一。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online