力扣.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%

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

小结

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

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

相关推荐
mldong5 小时前
引擎从不发一条消息:jeeflow 的两类扩展点与消息模块的分工
java·架构
君顾15 小时前
智慧零售实战指南:从技术架构到落地方案全解析
java·开发语言·零售
zhanghe6876 小时前
es的密码带有特殊字符,导出
java
pnoker6 小时前
IoT DC3 概念解读:把设备抽象成一套语义模板——位号、指令、事件与面向智能体的设备建模
java·人工智能·物联网·iot·dc3
my_realmy6 小时前
Java SE 基础学习笔记(一):面向对象、继承多态、抽象接口与异常(JDK 8)
java·多线程·并发··生产者消费者模式
天远数科7 小时前
风险治理实战:基于天远车信盟出险构建自动化理赔核保流水线
java·网络·人工智能·自动化
骇客野人7 小时前
SpringBoot电商系统用户注册、登录、留存及数据埋点设计与落地实施方案
java·spring boot·后端
qiuhaipeng17 小时前
Claude code 升级后上下文长度变短问题
java·前端·数据库
wxwx_bscxy3228 小时前
基于Python新冠疫情可视化分析系统的设计与实现
java·数据库·spring boot·python·微信小程序·sqlite
evans在进步9 小时前
Tomcat NIO 工作原理详解:从 Acceptor、Poller 到容器处理链
java·tomcat·nio