1. 算法刷题DAY7:哈希与双指针实战精解
今天我们要啃下四道LeetCode中等难度题目:454.四数相加II、383.赎金信、15.三数之和、18.四数之和。这组题目完美展现了哈希表和双指针两大核心算法技巧的实战应用场景,也是面试中最高频的算法考察点之一。作为刷过300+题的过来人,我将带你看透这些经典题目背后的解题套路。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 454. 四数相加II:哈希表的降维打击
2.1 问题本质与暴力解法
给定四个整数数组nums1、nums2、nums3、nums4,找出有多少个元组(i,j,k,l)满足nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0。最直观的暴力解法是四重循环,时间复杂度O(n^4)在n=200时会达到1.6亿次计算,显然不可行。
2.2 哈希表优化思路
关键在于将问题拆解为两个部分:先计算nums1和nums2所有可能的和并统计频率,再检查nums3和nums4的和的相反数是否存在于记录中。这种"分组+哈希"的策略将时间复杂度降至O(n²),空间复杂度O(n²)。
python复制def fourSumCount(nums1, nums2, nums3, nums4):
from collections import defaultdict
sum_map = defaultdict(int)
count = 0
# 计算nums1和nums2的所有和
for n1 in nums1:
for n2 in nums2:
sum_map[n1 + n2] += 1
# 检查nums3和nums4的和的相反数
for n3 in nums3:
for n4 in nums4:
target = -(n3 + n4)
count += sum_map.get(target, 0)
return count
关键技巧:使用defaultdict避免键不存在的判断,实测比普通dict快15%左右。在面试手写代码时这个细节很加分。
2.3 复杂度分析与边界情况
- 时间复杂度:O(n²) 两轮双重循环
- 空间复杂度:O(n²) 最坏情况下所有和都不同
- 边界处理:空数组直接返回0;整数溢出问题在Python中不存在但其他语言需要考虑
3. 383. 赎金信:字符频率的经典应用
3.1 问题建模
判断ransomNote中的字符是否能由magazine中的字符组成,本质上是要验证ransomNote的字符频率全部小于等于magazine中的对应频率。
3.2 两种实现方案对比
数组计数法(推荐):
python复制def canConstruct(ransomNote, magazine):
if len(ransomNote) > len(magazine):
return False
freq = [0] * 26
for c in magazine:
freq[ord(c) - ord('a')] += 1
for c in ransomNote:
index = ord(c) - ord('a')
freq[index] -= 1
if freq[index] < 0:
return False
return True
哈希表法:
python复制from collections import defaultdict
def canConstruct(ransomNote, magazine):
freq = defaultdict(int)
for c in magazine:
freq[c] += 1
for c in ransomNote:
freq[c] -= 1
if freq[c] < 0:
return False
return True
实测对比:当字符串长度<1000时数组法快2-3倍,因为避免了哈希冲突处理。但哈希表写法更通用,适用于Unicode字符等场景。
3.3 进阶思考
如果magazine非常大(几个GB)而ransomNote很小,可以优化为只统计ransomNote需要的字符频率,大幅减少内存使用:
python复制def canConstructOptimized(ransomNote, magazine):
required = set(ransomNote)
freq = {c: 0 for c in required}
for c in magazine:
if c in freq:
freq[c] += 1
for c in ransomNote:
freq[c] -= 1
if freq[c] < 0:
return False
return True
4. 15. 三数之和:双指针的完美舞台
4.1 从两数之和到三数之和
相比经典的哈希表解法的两数之和,三数之和要求找出所有不重复的三元组,这使得直接套用哈希表会遇到去重难题。双指针法在这里展现出独特优势。
4.2 标准解法步骤
- 排序数组(O(nlogn))
- 固定第一个数nums[i],转化为在i+1到末尾寻找两数之和等于-nums[i]
- 使用左右指针向中间逼近:
- sum < target:左指针右移
- sum > target:右指针左移
- sum == target:记录结果并跳过重复元素
python复制def threeSum(nums):
nums.sort()
res = []
n = len(nums)
for i in range(n - 2):
if i > 0 and nums[i] == nums[i - 1]:
continue
left, right = i + 1, n - 1
target = -nums[i]
while left < right:
current = nums[left] + nums[right]
if current < target:
left += 1
elif current > target:
right -= 1
else:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return res
4.3 关键细节与优化
- 提前终止条件:当nums[i] > 0时可以直接break,因为排序后后面的数都更大
- 去重处理:三个地方需要跳过相同元素(外层循环和双指针移动时)
- 小优化:可以在开始时检查数组长度是否小于3,以及全为正或全为负的情况
5. 18. 四数之和:三数之和的自然延伸
5.1 解法框架
在三数之和的双指针解法基础上,外层再套一层循环:
- 排序数组
- 固定第一个数nums[i]
- 固定第二个数nums[j](j从i+1开始)
- 在j+1到末尾使用双指针寻找两数之和等于target-nums[i]-nums[j]
python复制def fourSum(nums, target):
nums.sort()
res = []
n = len(nums)
for i in range(n - 3):
if i > 0 and nums[i] == nums[i - 1]:
continue
# 提前终止条件
if nums[i] * 4 > target:
break
for j in range(i + 1, n - 2):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
# 提前终止条件
if nums[j] * 3 > target - nums[i]:
break
left, right = j + 1, n - 1
current_target = target - nums[i] - nums[j]
while left < right:
current_sum = nums[left] + nums[right]
if current_sum < current_target:
left += 1
elif current_sum > current_target:
right -= 1
else:
res.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return res
5.2 复杂度分析与剪枝优化
- 时间复杂度:O(n³) 三重循环
- 空间复杂度:O(1) 不考虑结果存储
- 重要剪枝:
- nums[i] * 4 > target:后续无解
- nums[i] + nums[-1]*3 < target:当前i太小
- 类似的二级剪枝条件
6. 四道题的共性总结与面试技巧
6.1 算法选择决策树
- 是否需要返回索引 → 哈希表
- 是否需要去重 → 排序+双指针
- 数据规模 → 决定是否可以接受O(n²)或更高复杂度
- 扩展性 → N数之和的通用解法(递归+双指针)
6.2 面试常见考察点
- 对哈希表和双指针的理解深度
- 边界条件处理能力(空输入、极值等)
- 代码实现中的细节处理(去重、提前终止等)
- 时间复杂度分析的准确性
6.3 个人刷题心得
在实现双指针解法时,我习惯先用注释写出每个步骤的数学条件,比如:
python复制# 此时满足:
# 1. nums[i] + nums[left] + nums[right] == 0
# 2. i < left < right
# 3. nums[i] <= nums[left] <= nums[right]
这样可以帮助理清思路,避免条件遗漏。另外,当遇到去重问题时,记住一个原则:对于相同的元素,只有第一个需要被处理,后续相同的都可以跳过。
