18. 四数之和

244 阅读2分钟

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + 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 = [], target = 0
输出:[]

解法

排序+双指针 本题和15. 三数之和类似,解题思路也和三数之和一样。

主要思想:先用for遍历,列举前面两个数,然后用双指针列举最后两个数。

自己没看官方答案的实现如下:

def fourSum1(nums, target):
    n = len(nums)
    if n < 4: return []
    nums.sort()
    res = []  # 记录返回的数组

    # 枚举a
    for i in range(n):
        # 保证和上一次枚举的元素不相等
        # 这句代码不能要,因为有负数的情况,比如 nums=[1, -2, -5, -4, -3, 3, 3, 5] target=-11
        # if nums[i] > target: return res
        if i > 0 and nums[i] == nums[i - 1]:  # i>0 是要保证第一个数是参与运算了的
            continue
        for j in range(i+1, n):
            if j > i+1 and nums[j] == nums[j - 1]:  # j > i+1 是要保证j=i+1 那个数是参与运算了的
                continue
            # 使用双指针枚举b, c
            L, R = j + 1, n - 1
            while L < R:
                sum = nums[i] + nums[j] + nums[L] + nums[R]
                # 如果和为target,那么它就是最接近的和,直接返回
                if sum == target:
                    res.append([nums[i], nums[j], nums[L], nums[R]])  # 当满足四数之和等于target,就记录下来
                    while L < R and nums[L] == nums[L + 1]: L += 1  # 去除左边的重复元素
                    while L < R and nums[R] == nums[R - 1]: R -= 1  # 去除右边的重复元素
                    L += 1  # 满足了一组三数之和等于0之后,左右指针就向中间都移动一位,寻找新的组合
                    R -= 1
                elif sum < target:
                    # 如果和小于target,b 对应的指针向右移动一位
                    L += 1
                else:
                    # 如果和大于target,c 对应的指针向左移动一位
                    R -= 1
    return res

官方答案比我的更加优化,时间复杂度一样,只是增加了一些剪枝操作:

  • 在确定第一个数之后,如果nums[i]+nums[i+1]+nums[i+2]+nums[i+3]>target,说明此时剩下的三个数无论取什么值,四数之和一定大于target,因此退出第一重循环;

  • 在确定第一个数之后,如果nums[i]+nums[n−3]+nums[n−2]+nums[n−1]<target,说明此时剩下的三个数无论取什么值,四数之和一定小于target,因此第一重循环直接进入下一轮,枚举nums[i+1]

  • 在确定前两个数之后,如果nums[i]+nums[j]+nums[j+1]+nums[j+2]>target,说明此时剩下的两个数无论取什么值,四数之和一定大于target,因此退出第二重循环;

  • 在确定前两个数之后,如果nums[i]+nums[j]+nums[n−2]+nums[n−1]<target,说明此时剩下的两个数无论取什么值,四数之和一定小于target,因此第二重循环直接进入下一轮,枚举nums[j+1]

def fourSum2(nums, target):
    quadruplets = list()
    if not nums or len(nums) < 4:
        return quadruplets

    nums.sort()
    length = len(nums)
    for i in range(length - 3):
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target:
            break
        if nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target:
            continue
        for j in range(i + 1, length - 2):
            if j > i + 1 and nums[j] == nums[j - 1]:
                continue
            if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target:
                break
            if nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target:
                continue
            left, right = j + 1, length - 1
            while left < right:
                total = nums[i] + nums[j] + nums[left] + nums[right]
                if total == target:
                    quadruplets.append([nums[i], nums[j], nums[left], nums[right]])
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    left += 1
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1
                    right -= 1
                elif total < target:
                    left += 1
                else:
                    right -= 1

    return quadruplets
复杂度分析
  • 时间复杂度:O(n3)O(n^3),其中 n 是数组的长度。排序的时间复杂度是 O(nlogn)O(nlogn),枚举四元组的时间复杂度是 O(n3)O(n^3),因此总时间复杂度为 O(n3+nlogn)=O(n3)O(n^3+nlogn)=O(n^3)

  • 空间复杂度:O(logn)O(logn),其中 n 是数组的长度。空间复杂度主要取决于排序额外使用的空间。此外排序修改了输入数组 nums,实际情况中不一定允许,因此也可以看成使用了一个额外的数组存储了数组 nums 的副本并排序,空间复杂度为 O(n)O(n)

力扣官方答案