18. 四数之和

113 阅读5分钟

题目

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入: nums = [2,2,2,2,2], target = 8
输出: [[2,2,2,2]]

  提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

题目解析

思路:

解决这个问题的算法是回溯算法的一种扩展,称为 k-sum 问题。在 k-sum 问题中,我们尝试找到 k 个数的和等于 target 的所有组合。四数之和可以被看作是 k-sum 问题的一个特例,其中 k=4。

算法的核心思想是使用递归来减少问题的规模。即从四数之和减少为三数之和,再减少为两数之和,直至可以直接计算。对于两数之和,我们可以使用双指针法来优化搜索过程,这种方法比简单的双重循环更高效。

以下是解决问题的步骤:

  1. 对数组进行排序,这样我们就可以使用双指针法,并且方便跳过重复的数。
  2. 递归地解决 k-sum 问题,每一次递归 k 减一,直到转化为两数之和问题。
  3. 使用双指针法找到所有可能的两数组合,这些组合的和为当前的目标值。
  4. 如果找到合适的两数组合,就将其添加到结果列表中。
  5. 在递归过程中,跳过重复的数以避免重复的四元组出现在结果列表中。
class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        def kSum(nums: List[int], target: int, k: int) -> List[List[int]]:
            res = []
            # 处理边界情况
            if not nums:
                return res
            # 用于剪枝的平均值
            average_value = target // k
            # 如果平均值不在范围内,直接返回
            if average_value < nums[0] or average_value > nums[-1]:
                return res
            # 递归终止条件:2-sum问题
            if k == 2:
                return twoSum(nums, target)
            # 递归地解决 k-sum 问题
            for i in range(len(nums)):
                # 跳过重复的值
                if i == 0 or nums[i - 1] != nums[i]:
                    for subset in kSum(nums[i + 1:], target - nums[i], k - 1):
                        res.append([nums[i]] + subset)
            return res
        
        def twoSum(nums: List[int], target: int) -> List[List[int]]:
            res = []
            lo, hi = 0, len(nums) - 1
            while lo < hi:
                curr_sum = nums[lo] + nums[hi]
                if curr_sum < target or (lo > 0 and nums[lo] == nums[lo - 1]):
                    lo += 1
                elif curr_sum > target or (hi < len(nums) - 1 and nums[hi] == nums[hi + 1]):
                    hi -= 1
                else:
                    res.append([nums[lo], nums[hi]])
                    lo += 1
                    hi -= 1
            return res
        
        nums.sort()
        return kSum(nums, target, 4)

执行:

image.png

总结

四数之和问题是一个典型的组合搜索问题,它要求我们在一个数组中找到所有不重复的四元组,其和等于给定的目标值。这个问题是组合问题的一个实例,它可以扩展到通用的 k-sum 问题,即在数组中找到 k 个数字,其和为特定值的所有不重复组合。

适用情境: 此类方法适用于需要从一个集合中找到元素的所有可能组合以满足某些条件的问题。这包括但不限于数字和问题,比如两数之和、三数之和等,以及其他需要考虑元素组合的问题,如子集合问题、排列问题等。

使用的算法: 解决四数之和问题主要采用了回溯算法。回溯算法是一种通过探索所有可能的候选解来寻找所有解的算法。如果当前的候选解不满足目标条件,算法将回溯并尝试另一种可能的解。在四数之和问题中,我们通过递归地减少 k 的值来简化问题,直到问题简化为两数之和——一个可以通过双指针法有效解决的问题。

算法细节

  1. 首先对数组进行排序,排序是使用双指针法的前提,也方便跳过重复的值。
  2. 使用回溯法逐层减少问题规模,从四数之和减少到三数之和,再减少到两数之和。
  3. 在两数之和阶段,通过双指针法在有序数组中找到所有可能的配对,这比暴力枚举更高效。
  4. 在每一步,我们都检查并跳过重复的数字,以确保结果中不会包含重复的四元组。
  5. 通过递归,我们能够构建出所有满足条件的四元组。

总结: 四数之和问题通过将多数之和问题分解成多个两数之和问题,展示了回溯算法的强大能力。这种方法的优势在于其结构清晰,易于实现,且可以方便地扩展到其他类似的问题。此外,算法中的剪枝操作(比如跳过重复的数字)和排序后使用双指针的技巧显著提高了算法的效率。掌握了这种解法,我们不仅可以解决四数之和这样的具体问题,还能够应对更广泛的组合搜索问题,这对于提升我们的算法设计和编程能力非常有益。

题目链接

四数之和