18. 四数之和

目录

一、问题描述

二、解题思路

三、代码

四、复杂度分析


一、问题描述

给你一个由 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. 数组排序: 首先对数组进行升序排序,便于后续使用双指针法查找数对。同时,排序后的数组有利于去重处理,也能通过提前终止条件优化算法效率。

  2. 固定前两个数: 通过两层循环分别确定四个数中的前两个数 nums[i]nums[j]。此时,问题就简化成 "两数之和" 问题,即从剩余的数中找到两个数 nums[left]nums[right] 使它们的和等于 target - nums[i] - nums[j]

  3. 双指针寻找后两个数: 对于确定的前两个数 nums[i]nums[j],我们在剩下的数中通过双指针法,设定左右两个指针 leftright,从 nums[j+1]nums[length-1] 之间寻找满足条件的两个数。

    • 如果当前和等于目标值,则将其加入结果集。
    • 如果当前和小于目标值,则左指针右移,增大和。
    • 如果当前和大于目标值,则右指针左移,减小和。
  4. 去重处理: 为了避免重复的四元组,需要对数组中的重复元素进行过滤。对于每一层循环中的当前数值,如果和前一个数相等,则跳过该数,避免处理相同的组合。

  5. 提前终止条件: 由于数组已经排序,我们可以通过提前判断组合中最小值和最大值是否可能满足目标和,来减少不必要的计算:

    • 如果当前最小的四个数的和已经大于 target,后续更大的数肯定无法满足条件,直接跳出循环。
    • 如果当前最大的四个数的和还小于 target,则跳过这次循环,继续尝试更大的数。

三、代码

 class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> quadruplets = new ArrayList<>();
        
        // 如果数组长度小于4,直接返回空
        if (nums == null || nums.length < 4) {
            return quadruplets;
        }

        // 排序数组,方便去重和双指针处理
        Arrays.sort(nums);

        int length = nums.length;
        // 第一层循环,枚举第一个数
        for (int i = 0; i < length - 3; i++) {
            // 避免重复的第一个数
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 提前结束的优化条件1:当前组合的最小值已经大于target
            if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
                break;
            }
            // 提前跳过的优化条件2:当前组合的最大值还小于target
            if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
                continue;
            }

            // 第二层循环,枚举第二个数
            for (int j = i + 1; j < length - 2; j++) {
                // 避免重复的第二个数
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                // 提前结束的优化条件1:当前组合的最小值已经大于target
                if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                    break;
                }
                // 提前跳过的优化条件2:当前组合的最大值还小于target
                if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
                    continue;
                }

                // 双指针寻找剩余两个数
                int left = j + 1, right = length - 1;
                while (left < right) {
                    long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        // 跳过重复的第三个数
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        left++;

                        // 跳过重复的第四个数
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        right--;
                    } else if (sum < target) {
                        left++;  // 当前和小于target,左指针右移
                    } else {
                        right--;  // 当前和大于target,右指针左移
                    }
                }
            }
        }

        return quadruplets;
    }
}

四、复杂度分析

时间复杂度:
  1. 排序的时间复杂度O(n log n),因为我们需要先对数组进行排序。
  2. 双重循环的时间复杂度
    • 第一层循环 i 需要遍历 n - 3 次。
    • 第二层循环 j 需要遍历 n - i - 2 次。
    • 内部的双指针部分在每次固定 ij 后,最多需要遍历 O(n) 次,进行一次线性扫描。
    因此,双重循环的时间复杂度为 O(n^3),综合排序时间,整个算法的时间复杂度为 O(n^3)
空间复杂度:
  1. 额外空间使用:在排序之后,除了存储结果的 quadruplets 列表,算法几乎没有使用额外的空间。
  2. 空间复杂度O(n),主要用于存储结果四元组的列表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值