力扣.15 三数之和 three-sum

数组系列

力扣数据结构之数组-00-概览

力扣.53 最大子数组和 maximum-subarray

力扣.128 最长连续序列 longest-consecutive-sequence

力扣.1 两数之和 N 种解法 two-sum

力扣.167 两数之和 II two-sum-ii

力扣.170 两数之和 III two-sum-iii

力扣.653 两数之和 IV two-sum-IV

力扣.015 三数之和 three-sum

力扣.016 最接近的三数之和 three-sum-closest

力扣.259 较小的三数之和 three-sum-smaller

题目

给你一个整数数组 nums ,判断是否存在三元组 nums\[i, numsj, numsk] 满足 i != j、i != k 且 j != k ,同时还满足 numsi + numsj + numsk == 0 。

请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

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

解释: nums0 + nums1 + nums2 = (-1) + 0 + 1 = 0 。 nums1 + nums2 + nums4 = 0 + 1 + (-1) = 0 。 nums0 + nums3 + nums4 = (-1) + 2 + (-1) = 0 。 不同的三元组是 -1,0,1-1,-1,2

注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = 0,1,1 输出:\[\] 解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = 0,0,0 输出:\[0,0,0]

解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000

-10^5 <= numsi <= 10^5

前言

这道题作为 leetcode 的第 15 道题,看起来似曾相识。

大概思路可以有下面几种:

  1. 暴力解法

  2. 数组排序+二分

  3. Hash 优化

  4. 双指针

v1-暴力解法

思路

直接 3 次循环,找到符合结果的数据返回。

这种最容易想到,一般工作中也是我们用到最多的。

当然也必定超时。

实现

java 复制代码
class Solution {
   
   public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> res = new HashSet<>();

        final int n = nums.length;
        for(int i = 0; i < n; i++){
            for(int j = i+1; j < n; j++) {
                for(int k = j+1; k < n; k++) {
                    if(nums[i]+nums[j]+nums[k] == 0) {
                        List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
                        Collections.sort(list);
                        res.add(list);
                    }
                }
            }
        }

        return new ArrayList<>(res);
    }

}

效果

超出时间限制

308 / 313 个通过的测试用例

小结

这里慢在几个地方:

1)三层循环,找到符合的数据

2)数据需要去重,所以用到了排序,虽然是一个小排序。

v2-思维的转换

思路

我们把问题这么考虑

要找的数其实是:nums[i] + nums[j] + nums[k] == 0

那么,我们如果固定一个值:

那么问题就变成了

nums[j] + nums[k] == -nums[i]

也就是变成了我们的 T001/T167 的题目。

疑问 数据去重问题呢?

暂时先不考虑,过会根据测试用例优化

编程思路

我们定义两个指针

ini 复制代码
left=0
right=n-1
sum=num[left]+num[right-1]

因为数组有有序的,所以只有 3 种情况:

  1. sum == target 直接满足

  2. sum < target,left++

  3. sum > target, right--

实现

java 复制代码
class Solution {
   
   public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);

        Set<List<Integer>> res = new HashSet<>();

        final int n = nums.length;
        // 因为是有序的,从前面找2个数字,等于当前数字更加合理。
        // nums[j] + nums[k] == -nums[i]

        for(int i = 0; i < n; i++){
            int target = -nums[i];

            int left = 0;
            int right = n-1;

            // 找两个数
            while (left < right) {
                int sum = nums[left]+nums[right];
                if(sum == target) {
                    // 排序+去重
                    if(i != left && left != right && i != right) {
                        List<Integer> list = Arrays.asList(nums[left], nums[right], nums[i]);
                        Collections.sort(list);
                        res.add(list);
                    }
                }
                if(sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }

        return new ArrayList<>(res);
    }

}

效果

超出时间限制 312 / 313 个通过的测试用例

小结

最大的问题还是我们为什么要去重?为什么这么麻烦

v3-去重

思路

数据重复存在两个问题:

1)0, 1, -11, 0, -1 认为重复

所以我们在固定第一个元素的时候,直接跳过 numsi == numsi-1,可以解决初始值重复的问题。

2)数组排序后存在重复的数据,那么我们只需要跳过重复的元素即可

我们的 left right 指针移动的时候,也需要跳过重复

初始值的问题

我们固定第一个数 numi,下标从 0, 1, ..., n-3

剩下的两个数:从 i+1, ..., n-1 中选择

代码

java 复制代码
public List<List<Integer>> threeSum(int[] nums) {
    Arrays.sort(nums);
    List<List<Integer>> res = new ArrayList<>();
    final int n = nums.length;
    for(int i = 0; i < n-2; i++){
        // 跳过重复的第一个数
        if(i > 0 && nums[i] == nums[i-1]) {
            continue;
        }

        // 目标值
        int left = i+1;
        int right = n-1;

        // 双指针
        while (left < right) {
            int sum = nums[i] + nums[left]+nums[right];
            if(sum == 0) {
                List<Integer> list = Arrays.asList(nums[i], nums[left], nums[right]);
                res.add(list);
                // 左右避免数据重复
                while (left < right && nums[left] == nums[left+1]) {
                    left++;
                }
                while (left < right && nums[right] == nums[right-1]) {
                    right--;
                }
            }
            if(sum < 0) {
                left++;
            } else {
                right--;
            }
        }
    }
    return res;
}

效果

32ms 62.27%

效果还行。看了下基本实现就是这个。

小结

这里对双指针的理解要求比较高。

而且对于重复性数据的判断技巧要求特别高,算得上是一道很接近困难的题目

相关推荐
Web Security Loop15 分钟前
MAC卸载JDK环境
java·macos·jdk
Rain的Java大神之路1 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
counting money2 小时前
Tomcat 专题:从基础架构到性能调优与 WebSocket 实战
java·websocket·tomcat
xiaohaiAIgeo2 小时前
【2026年】HG/T 20656-2024化工暖通空调设计规范:新版标准的变化与影响
java·前端·javascript·科普知识
小强库计算机毕业设计3 小时前
基于 Spring Boot + Vue3 的校园管理系统
java·spring boot·后端·项目实战·管理系统·技术分享·校园管理系统实战
霸道流氓气质3 小时前
Java中信号量(Semaphore):从本地到分布式
java·开发语言·分布式
怪奇云呼军3 小时前
G.711、Opus 和重采样会拖慢识别吗?闪电智能VoiceAgent 的音频入口怎么选
java·人工智能·python·算法·云计算·音视频
编程令我快乐3 小时前
maven部分构建参数讲解
java·maven
ZC跨境爬虫3 小时前
LeetCode 27. 移除元素(双指针详解 + Java Python 多解法对比)
java·python·leetcode
kisbad4 小时前
Day 036|OpenAI Agents SDK 快速开始:今天跑通第一个 Agent
java·前端·javascript