作为一名正在刷题找工作的程序员,我深知算法题中两数之和和三数之和这类经典问题的重要性。这两道题不仅是面试高频考点,更是理解双指针算法的最佳入门案例。本文将结合灵茶山艾府老师的讲解,深入剖析这两道题的解题思路、代码实现和优化技巧。
LeetCode 167题"两数之和 II - 输入有序数组"要求我们在一个已排序的数组中找到两个数,使它们的和等于特定目标值。题目给出了三个关键信息:
这些信息直接决定了我们的解题策略。由于数组已排序,我们可以利用这一特性避免暴力解法,采用更高效的双指针方法。
相向双指针算法的核心思想是:一个指针从数组起始位置开始(左指针),另一个指针从数组末尾开始(右指针),通过比较两数之和与目标值的关系,逐步向中间移动指针。
具体步骤:
这个算法的时间复杂度是O(n),空间复杂度是O(1),是最优解法。
注意:由于数组已排序,当s>target时只能移动右指针,s<target时只能移动左指针。这个特性是算法正确性的关键保证。
python复制class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
s = numbers[left] + numbers[right]
if s == target:
return [left + 1, right + 1] # 题目要求返回的是位置(下标+1)
if s > target:
right -= 1 # 和太大,右指针左移
else:
left += 1 # 和太小,左指针右移
代码细节说明:
LeetCode 15题"三数之和"要求找出所有不重复的三元组,使得三个数之和为0。与两数之和相比,这道题有三个主要区别:
解决思路是将三数之和转化为两数之和问题:
python复制class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans = []
n = len(nums)
for i in range(n-2): # 留两个位置给j和k
x = nums[i]
# 跳过重复的i值
if i > 0 and x == nums[i-1]:
continue
j, k = i+1, n-1
while j < k:
s = x + nums[j] + nums[k]
if s > 0:
k -= 1
elif s < 0:
j += 1
else:
ans.append([x, nums[j], nums[k]])
j += 1
# 跳过重复的j值
while j < k and nums[j] == nums[j-1]:
j += 1
k -= 1
# 跳过重复的k值
while k > j and nums[k] == nums[k+1]:
k -= 1
return ans
在基本解法基础上,我们可以添加两个重要的提前终止条件,显著提升算法效率:
最小和检查:如果当前固定的数nums[i]加上后面两个最小的数(nums[i+1]+nums[i+2])已经大于0,那么后面的组合只会更大,可以直接break整个循环。
最大和检查:如果当前固定的数nums[i]加上最后两个最大的数(nums[-2]+nums[-1])仍然小于0,说明这个i值太小,应该continue到下一个i值。
优化后的代码:
python复制class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
ans = []
n = len(nums)
for i in range(n-2):
x = nums[i]
if i > 0 and x == nums[i-1]:
continue
# 优化1:最小和检查
if x + nums[i+1] + nums[i+2] > 0:
break
# 优化2:最大和检查
if x + nums[-2] + nums[-1] < 0:
continue
j, k = i+1, n-1
while j < k:
s = x + nums[j] + nums[k]
if s > 0:
k -= 1
elif s < 0:
j += 1
else:
ans.append([x, nums[j], nums[k]])
j += 1
while j < k and nums[j] == nums[j-1]:
j += 1
k -= 1
while k > j and nums[k] == nums[k+1]:
k -= 1
return ans
时间复杂度:O(n)
空间复杂度:O(1)
时间复杂度:O(n^2)
空间复杂度:取决于排序实现
优化点对性能的影响:
在实际测试中,优化后的解法可以比基本解法快2-3倍,特别是在有大量数据无法满足条件的情况下。
空数组或长度不足:
重复元素处理:
打印中间结果:
小规模测试用例:
极端情况测试:
指针移动错误:
重复解处理不当:
下标越界:
同样的思路可以扩展到四数之和问题:
LeetCode 16题"最接近的三数之和"可以使用类似方法:
这类算法在实际中有广泛应用:
在刷这两道题的过程中,我总结了以下几点经验:
排序是双指针算法的前提:大多数情况下,先排序能让问题变得更简单,虽然排序需要O(nlogn)时间,但往往能换来更高效的解法。
指针移动要有明确逻辑:每次移动指针都必须有充分的理由,不能随意移动。在两数之和中,移动指针的依据是和与目标值的关系。
去重是容易被忽略的细节:特别是在三数之和这类问题中,去重逻辑需要仔细处理,否则会得到大量重复解。
提前终止能显著提升性能:像三数之和中的最小和与最大和检查,虽然看起来是小的优化,但在实际运行中能大幅减少不必要的计算。
从简单问题入手:理解了两数之和的双指针解法后,三数之和就变成了在两数之和基础上增加一个外层循环,这种循序渐进的学习方式很有效。
最后,建议在理解这些解法后,尝试自己从头实现几次,并在LeetCode上提交测试。只有通过实际编码和调试,才能真正掌握这些算法的精髓。