力扣hot100----15.三数之和(java版)

1、题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

示例 1:

复制代码
 输入:nums = [-1,0,1,2,-1,-4]
 输出:[[-1,-1,2],[-1,0,1]]
 解释:
 nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
 nums[0] + nums[3] + nums[4] = (-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 。

2、思路

方法一:暴力破解

java 复制代码
class Solution{
	public List<List<Integer>> threeSum(int[] nums){
		Set<List<Integer>> resultSet = new HashSet<>();//使用Set自动去重
		//三重循环:i,j,k互不相同
		for(int i = 0;i<nums.length -2;i++){
			for(int j = i+1;j<nums.length-1;j++){
				for(int k = j+ 1;k<nums.length;k++){
					if(nums[i]+nums[j]+nums[k] == 0){
						//创建一个三元组,排序后加入Set(避免[0,-1,1]和[-1,0,-1]被认为不同)
						List<Integer> triplet = Arrays.asList(nums[i],nums[j],nums[k]);
						Collections.sort(triplet);//排序保证顺序一致
						resultSet.add(triplet);
					}
				}
			}
		}
		//转成List返回
		return new ArrayList<>(resultSet);
	}
}

/**Arrays.asList(...)

将一组元素(或一个数组)快速包装成一个固定大小的 List,便于临时使用或传递给需要集合参数的方法。

*/

**注意:**这种暴力破解的方法是可以的,但是在算法或者数据规模较大的情况下,会超出时间限制,因为它的时间复杂度是O(n^3),空间复杂度为O(1)

方法二:排序 + 双指针(Two Pointers)

时间复杂度:O(n²)

空间复杂度:O(1)(不考虑结果存储空间)

步骤 1:排序 ------ 为什么?
java 复制代码
 Arrays.sort(nums);

为什么排序?

  • 排序后,我们可以使用双指针技巧来高效查找两个数之和等于目标值。

  • 排序后,可以轻松跳过重复元素,避免重复三元组。

  • 排序是双指针法的前提。

时间复杂度:O(n log n)

步骤 2:外层循环固定第一个数 ------ 为什么?
java 复制代码
for (int i = 0; i < nums.length - 2; i++) {
     if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复
     ...
 }

为什么从 0 到 len-2?

  • 因为要留出两个位置给***left*** 和*right* 指针。

  • 边界:i < nums.length - 2

为什么跳过重复?

  • 如果***nums[i] == nums[i-1]***,说明以这个数开头的组合我们已经处理过了,跳过避免重复三元组。
步骤 3:双指针找另外两个数 ------ 为什么?
java 复制代码
 int left = i + 1, right = nums.length - 1;
 while (left < right) {
     int sum = nums[i] + nums[left] + nums[right];
     if (sum < 0) left++;
     else if (sum > 0) right--;
     else {
         // 找到一组解
         list.add(Arrays.asList(nums[i], nums[left], nums[right]));
         // 跳过重复
         while (left < right && nums[left] == nums[left + 1]) left++;
         while (left < right && nums[right] == nums[right - 1]) right--;
         left++;
         right--;
     }
 }

为什么双指针能工作?

  • 数组已排序,所以:

    • 如果当前三数之和 太小 → 需要更大的数 → left++

    • 如果当前三数之和 太大 → 需要更小的数 →right--

    • 如果 等于 0 → 找到答案,记录并移动双指针继续找

为什么移动指针时还要跳过重复?

  • 例如:[-2, 0, 0, 2, 2],当 i=0left=1, right=4 ,找到 [-2,0,2]

  • 如果不跳过,left=2, right=3 又会找到相同的 [-2,0,2] → 重复!

边界条件(Corner Cases)

输入 说明 处理方式
nums.length < 3 不足三个数 直接返回空列表
全是 0:[0,0,0,0] 只能有一个 [0,0,0] 通过跳过重复处理
全是正数或负数 不可能和为 0 双指针自然不会找到解
有重复元素 [-1,-1,0,1] 通过 i > 0 && nums[i]==nums[i-1] 跳过
Java 数据结构与算法知识点详解
(1) List<List<Integer>>
java 复制代码
 List<List<Integer>> result = new ArrayList<>();

知识点:

  • Java 泛型:List<List<Integer>> 表示"列表的列表",每个元素是一个 List<Integer>

  • ArrayList 是***List***接口的常用实现,支持动态扩容。

  • 添加元素:result.add(Arrays.asList(a, b, c)) ------ 快速创建不可变列表。

(2)Arrays.sort(int[])
java 复制代码
 Arrays.sort(nums);

知识点

  • 对原数组进行升序排序

  • 底层使用双轴快排(Dual-Pivot QuickSort),平均 O(n log n),不稳定排序。

  • 原地排序,不额外占用空间。

(3) Arrays.asList(...)
java 复制代码
list.add(Arrays.asList(nums[i], nums[left], nums[right]));

知识点

  • 快速将多个元素包装成 List

  • 返回的是固定大小的列表 (基于数组),不能 add/remove,但这里只是用于添加到外层列表,没问题。

  • 如果需要可变列表,可以用***new ArrayList<>(Arrays.asList(...))***,但没必要。

(4)双指针(Two Pointers)算法

核心思想:

  • 有序数组中,用两个指针从两端向中间逼近,根据条件移动指针。

  • 常用于:

    • 两数之和

    • 三数之和

    • 最接近的三数之和

    • 盛最多水的容器

优势:

  • 时间复杂度从 O(n²) 降到 O(n)(在固定第一个数的前提下)

  • 空间复杂度 O(1)

完整代码
java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        //边界:不足三个数
        if (nums == null || nums.length < 3) {
            return result;
        }
        //1、排序
        Arrays.sort(nums);
        //2、遍历一个数
        for (int i = 0; i < nums.length - 2; i++) {
            //跳过重复的第一个数
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;
            //3、双指针找另外两个数
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0) {
                    left++;//和太小,左指针右移
                } else if (sum > 0) {
                    right--;//和太小,右指针左移
                } else {
                    //找到一组解
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    //跳过重复的left和right
                    while (left < right && nums[left] == nums[left + 1])
                        left++;
                    while (left < right && nums[right] == nums[right - 1])
                        right--;
                    //移动指针继续查找
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
}
相关推荐
程序员卷卷狗8 小时前
JVM 调优实战:从线上问题复盘到精细化内存治理
java·开发语言·jvm
cj6341181509 小时前
【MySQL】mysqldump使用方法
java·后端
JIngJaneIL9 小时前
停车场管理|停车预约管理|基于Springboot的停车场管理系统设计与实现(源码+数据库+文档)
java·数据库·spring boot·后端·论文·毕设·停车场管理系统
杰克尼9 小时前
二分查找为什么总是写错
java·数据结构·算法
半旧夜夏11 小时前
【分布式缓存】Redis持久化和集群部署攻略
java·运维·redis·分布式·缓存
短视频矩阵源码定制11 小时前
矩阵系统源码推荐:技术架构与功能完备性深度解析
java·人工智能·矩阵·架构
Eiceblue11 小时前
使用 Java 将 Excel 工作表转换为 CSV 格式
java·intellij-idea·excel·myeclipse
漂流幻境11 小时前
IntelliJ IDEA的Terminal中执行ping命令时遇到的“No route to host“问题
java·ide·intellij-idea
苹果醋312 小时前
element-ui源码阅读-样式
java·运维·spring boot·mysql·nginx