大数跨境
0
0

LeetCode 18四数之和详解:排序+双指针解法与题型拓展

LeetCode 18四数之和详解:排序+双指针解法与题型拓展 点点技术屋
2025-11-24
0
四数之和问题的核心挑战

LeetCode 18题"四数之和"要求我们在一个包含n个整数的数组中,找出所有不重复的四元组(a,b,c,d),使得它们的和等于目标值target。这个问题看似只是"三数之和"的简单扩展,但实际操作中会遇到更多细节陷阱。

先看题目给出的关键约束:

  • 数组长度n可以达到200(比三数之和的1000小,但四元组组合复杂度更高)

  • 答案中不允许包含重复的四元组

  • 数组元素可能包含负数和零

  • 目标值target可以是任意整数(包括负数)

最直观的暴力解法是四层嵌套循环,时间复杂度达到O(n⁴),这在n=200时会产生200⁴=1.6亿次运算,显然会超时。因此我们需要更高效的解法。

排序+双指针解法的核心思路

四数之和的最优解法依然是排序+双指针的组合策略,但比三数之和多了一层循环控制。基本思路是:

  1. 先对数组进行排序(O(n log n)时间)

  2. 固定两个数(通过两层循环)

  3. 用双指针寻找剩下的两个数,将问题降为两数之和问题

  4. 全程进行去重处理,避免重复四元组

上图清晰展示了算法的执行流程:

  • 外层i循环固定第一个数

  • 中层j循环固定第二个数

  • 内层left和right双指针从两端向中间移动,寻找和为target-nums[i]-nums[j]的两个数

  • 每次找到符合条件的四元组后,进行去重操作并记录结果

这种解法的时间复杂度是O(n³),空间复杂度是O(1)(不考虑存储结果的空间)。

Python实现代码与详细注释

def fourSum(nums, target):    # 结果列表    result = []    # 获取数组长度    n = len(nums)    # 数组长度小于4时直接返回空列表    if n < 4:        return result
    # 对数组进行排序,为双指针和去重做准备    nums.sort()
    # 外层循环:固定第一个数    for i in range(n - 3):        # 第一个数去重        if i > 0 and nums[i] == nums[i - 1]:            continue
        # 优化:如果当前最小的四数之和已经大于target,无需继续        if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target:            break
        # 优化:如果当前最大的四数之和还小于target,跳过当前i        if nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target:            continue
        # 中层循环:固定第二个数        for j in range(i + 1, n - 2):            # 第二个数去重            if j > i + 1 and nums[j] == nums[j - 1]:                continue
            # 优化:如果当前最小的四数之和已经大于target,无需继续            if nums[i] + nums[j] + nums[j+1] + nums[j+2] > target:                break
            # 优化:如果当前最大的四数之和还小于target,跳过当前j            if nums[i] + nums[j] + nums[n-2] + nums[n-1] < target:                continue
            # 双指针初始化            left = j + 1  # 左指针从j+1开始            right = n - 1  # 右指针从数组末尾开始
            # 双指针寻找剩下的两个数            while left < right:                current_sum = nums[i] + nums[j] + nums[left] + nums[right]
                # 找到符合条件的四元组                if current_sum == target:                    # 添加到结果列表                    result.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
                # 当前和小于目标值,左指针右移增大和                elif current_sum < target:                    left += 1
                # 当前和大于目标值,右指针左移减小和                else:                    right -= 1
    return result

去重逻辑深度分析

四数之和问题中最容易出错的部分就是去重逻辑。与三数之和相比,四数之和需要对两个固定指针(i和j)都进行去重处理,这增加了复杂度。

四种指针的去重策略

  1. i指针去重

 

if i > 0and nums[i] == nums[i - 1]:       continue

注意i必须从1开始比较,避免越界。当i与i-1相等时跳过当前值。

  1. j指针去重

 

if j > i + 1and nums[j] == nums[j - 1]:       continue

j的去重起始条件是j > i + 1,确保j至少从i+1的下一个位置开始比较。

  1. left指针去重

 

while left < right and nums[left] == nums[left + 1]:        left += 1    left += 1# 最后再移动一次指针

找到符合条件的四元组后,将left移到最后一个重复元素的位置,然后再+1。

  1. right指针去重

 

while left < right and nums[right] == nums[right - 1]:        right -= 1    right -= 1# 最后再移动一次指针

类似地,将right移到最后一个重复元素的位置,然后再-1。

去重的常见错误与解决方案

  • 错误1:在找到目标和之前就进行去重

 

# 错误示例while left < right and nums[left] == nums[left + 1]:       left += 1

解决方案:只有在找到符合条件的四元组后才进行去重操作

  • 错误2:去重后忘记移动指针

 

# 错误示例while left < right and nums[left] == nums[left + 1]:       left += 1# 缺少 left += 1

解决方案:去重循环后必须手动移动一次指针

  • 错误3:i和j的去重条件不正确

 

# 错误示例if nums[i] == nums[i + 1]:  # 应该和i-1比较而不是i+1continue

解决方案:i应该和i-1比较,j应该和j-1比较

边界情况处理策略

四数之和问题有许多边界情况需要特殊处理,否则容易导致错误或超时:

1. 数组长度不足4的情况

 

if n < 4:    return result

直接返回空列表,因为无法组成四元组。

2. 整数溢出问题

在Java等语言中,四个整数相加可能会超出int范围,需要使用long类型:

 

longcurrentSum= (long) nums[i] + nums[j] + nums[left] + nums[right];

3. 提前终止循环的优化

 

# 最小四数之和大于target,后续不可能有解if nums[i] + nums[i+1] + nums[i+2] + nums[i+3] > target:    break# 当前i对应的最大四数之和小于target,跳过当前iif nums[i] + nums[n-3] + nums[n-2] + nums[n-1] < target:    continue

这两个优化可以大幅减少不必要的循环,特别是在数组元素较大或较小时。

4. 处理重复元素密集的数组

当数组中有大量重复元素时,去重逻辑尤为重要。例如数组[0,0,0,0,0],target=0,正确答案应该只有一个四元组[0,0,0,0]。

5. 处理target为负数的情况

与三数之和不同,四数之和的target可以是负数,这时候不能简单地通过判断nums[i] > target就跳出循环,因为后续的负数相加可能会使总和减小到target。

k数之和的通用解题思路

四数之和是k数之和问题的一个特例,我们可以将其推广为更通用的k数之和解法。

k数之和的递归解法

基本思路是通过递归将k数之和问题逐步降为(k-1)数之和、(k-2)数之和,直到2数之和问题,然后用双指针求解:

def kSum(nums, target, k):    # 排序数组    nums.sort()    # 结果列表    result = []    # 调用递归函数    backtrack(nums, target, k, 0, [], result)    return result
def backtrack(nums, target, k, start, path, result):    # 递归终止条件:k=2时使用双指针    if k == 2:        leftright = start, len(nums) - 1        while left < right:            current_sum = nums[left+ nums[right]            if current_sum == target:                result.append(path + [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            elif current_sum < target:                left += 1            else:                right -= 1        return
    # 递归处理k>2的情况    for i in range(start, len(nums) - k + 1):        # 去重        if i > start and nums[i] == nums[i - 1]:            continue
        # 优化:如果当前最小的k数之和已经大于target,无需继续        if sum(nums[i:i+k]) > target:            break
        # 优化:如果当前最大的k数之和还小于target,跳过当前i        if nums[i] + sum(nums[-k+1:]) < target:            continue
        # 递归调用,k减1start设为i+1,路径添加当前元素        backtrack(nums, target - nums[i], k - 1, i + 1, path + [nums[i]], result)


k数之和的时间复杂度分析

  • 排序时间:O(n log n)

  • 递归调用次数:O(n^(k-1)),对于四数之和是O(n³)

  • 双指针部分:O(n)

  • 总时间复杂度:O(n log n + n^(k-1))

当k=2时,时间复杂度为O(n log n);当k=3时,为O(n²);当k=4时,为O(n³),依此类推。

k数之和的空间复杂度分析

  • 递归调用栈深度:O(k)

  • 存储结果的空间:O(C(n,k)),最坏情况下是O(n^k)

  • 总空间复杂度:O(n^k)

算法优化技巧

为了进一步提高四数之和算法的效率,可以采用以下优化技巧:

1. 提前终止循环

在每层循环中,如果当前最小可能的和已经大于target,或者当前最大可能的和还小于target,可以直接终止或跳过当前循环:


2. 处理大规模重复元素

当数组中存在大量重复元素时,可以通过计数来减少循环次数:

然后在循环时考虑每个数字的出现次数,避免对重复元素进行多次循环。

3. 剪枝优化

在递归实现的k数之和中,可以通过剪枝进一步减少不必要的计算:

总结与拓展

四数之和问题是对排序+双指针技巧的进一步深化应用,通过固定两个数并使用双指针寻找另外两个数,将时间复杂度从O(n⁴)降低到O(n³)。

关键知识点回顾

  1. 排序+双指针是解决n数之和问题的通用方法

  2. 去重逻辑是保证结果正确性的关键,需要对四个指针分别处理

  3. 边界情况处理直接影响算法的健壮性

  4. 提前终止循环可以显著提高算法效率

  5. k数之和可以通过递归降维为2数之和问题

相关题目推荐

  1. LeetCode 1 两数之和:基础的两数之和问题,可使用哈希表解法

  2. LeetCode 15 三数之和:四数之和的简化版,固定一个数+双指针

  3. LeetCode 16 最接近的三数之和:类似问题,但要求找到最接近目标的和

  4. LeetCode 454 四数相加II:四个数组的四数之和问题,可使用哈希表优化

算法思维拓展

四数之和问题体现了降维思想——将高维问题转化为低维问题求解。这种思想在算法设计中非常重要:

  • 空间降维:如将二维数组问题转化为一维数组问题

  • 时间降维:如将四重循环降为三重循环

  • 问题降维:如将k数之和降为2数之和

掌握降维思想可以帮助我们解决更多复杂问题,提高算法效率。

通过本文的学习,相信你已经掌握了四数之和问题的核心解法和优化技巧。这个问题虽然看似简单,但其中包含了丰富的算法思想和编程细节,值得我们深入研究和思考。

【声明】内容源于网络
0
0
点点技术屋
写自己擅长的Nginx,Linux,Redis,JVM和Golang 写自己学习的算法,运维总结 写一些散文随笔 写我最爱的大熊猫,发大熊猫的图片
内容 120
粉丝 0
点点技术屋 写自己擅长的Nginx,Linux,Redis,JVM和Golang 写自己学习的算法,运维总结 写一些散文随笔 写我最爱的大熊猫,发大熊猫的图片
总阅读25
粉丝0
内容120