当前位置: 首页 > article >正文

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),主要用于存储结果四元组的列表。

http://www.kler.cn/news/335826.html

相关文章:

  • 设计模式~~~
  • Android Studio 新版本 Logcat 的使用详解
  • 力扣之1322.广告效果
  • 大数据新视界 --大数据大厂之 从 Druid 和 Kafka 到 Polars:大数据处理工具的传承与创新
  • 医院综合服务系统小程序的设计
  • 区间合并算法详解
  • 企业架构TOGAF的理论指南:数字化转型中的企业架构实践
  • SemiDesgin中后台组件库,字节跳动出品,能否火,有待检验。
  • JS中浅拷贝和深拷贝的区别
  • 3.使用条件语句编写存储过程(3/10)
  • 企业人力资源管理,人事档案管理,绩效考核,五险一金,招聘培训,薪酬管理一体化管理系统(源码)
  • 《Windows PE》4.2 绑定导入表
  • Pytest 使用Pycharm右键直接运行测试脚本正常,控制台命令pytest运行收集不到用例无法正常测试 no tests ran in 0.01s
  • Python知识点:在Python环境中,如何使用Transformers进行预训练语言模型应用
  • 目标检测 DETR(2020)
  • 【Linux】信号知识三把斧——信号的产生、保存和处理
  • Vue - 路由用法
  • 基于 springboot vue中学生日常行为评分管理系统设计与实现
  • Python 进阶部分详细整理
  • 第五十九周周报 IAGNN