18. 四数之和

目录

一、问题描述

二、解题思路

三、代码

四、复杂度分析


一、问题描述

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

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • 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,则跳过这次循环,继续尝试更大的数。

三、代码

java 复制代码
 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),主要用于存储结果四元组的列表。
相关推荐
麻花20139 分钟前
WPF里面的C1FlexGrid表格控件添加RadioButton单选
java·服务器·前端
理想不理想v23 分钟前
【经典】webpack和vite的区别?
java·前端·javascript·vue.js·面试
请叫我青哥26 分钟前
第五十二条:谨慎使用重载
java·spring
2402_8713219538 分钟前
MATLAB方程组
gpt·学习·线性代数·算法·matlab
疯一样的码农1 小时前
Apache Maven简介
java·maven·apache
Mongxin_Chan1 小时前
【Cpp】指针与引用
c++·算法
小安同学iter1 小时前
Java进阶五 -IO流
java·开发语言·intellij-idea
非自律懒癌患者1 小时前
Transformer中的Self-Attention机制如何自然地适应于目标检测任务
人工智能·算法·目标检测
尽兴-1 小时前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
SSL_lwz1 小时前
P11290 【MX-S6-T2】「KDOI-11」飞船
c++·学习·算法·动态规划