【Hot100 | 6 LeetCode 15. 三数之和】

这段代码是解决 LeetCode 15. 三数之和 问题的经典排序 + 双指针解法,核心目标是在整数数组中找到所有不重复的三元组(a, b, c),使得 a + b + c = 0。该解法通过排序简化查找逻辑,结合双指针优化效率,并通过严格去重避免重复结果,时间复杂度为 O (n²),空间复杂度为 O (log n)(主要来自排序)。

一、问题理解

问题要求

给定一个整数数组 nums,返回所有和为 0 的三元组 [nums[i], nums[j], nums[k]],满足:

  • i、j、k 是不同的索引(i ≠ j、i ≠ k、j ≠ k);
  • 结果中不能包含重复的三元组 (如 [-1, 0, 1][0, -1, 1] 视为相同,需去重)。

二、核心思路:排序 + 双指针 + 去重

暴力解法(三重循环枚举所有三元组)时间复杂度为 O (n³),且需要额外去重,效率极低。该解法通过以下优化实现高效求解:

  1. 排序:先对数组排序,好处有二:

    • 方便利用双指针缩小查找范围(根据和的大小移动指针);
    • 让相同元素相邻,便于去重(避免重复三元组)。
  2. 固定一元素 + 双指针找另外两元素

    • 固定第一个元素 nums[i](i 从 0 到 n-3);
    • 用左指针 left = i+1、右指针 right = n-1 寻找另外两个元素,使得三者和为 0。
  3. 根据和调整指针

    • nums[i] + nums[left] + nums[right] == 0:找到符合条件的三元组,加入结果;
    • 若和 < 0:总和偏小,左指针右移(增大数值);
    • 若和 > 0:总和偏大,右指针左移(减小数值)。
  4. 去重逻辑:对固定元素 i、左指针 left、右指针 right 分别去重,避免重复三元组。

三、代码逐行解析

java

运行

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        // 1. 初始化结果列表(存储所有符合条件的三元组)
        List<List<Integer>> ans = new ArrayList<>();
        
        // 2. 边界处理:数组为空或长度不足3,直接返回空列表
        if (nums == null || nums.length < 3) {
            return ans;
        }
        
        // 3. 对数组排序(核心前提:方便双指针查找和去重)
        Arrays.sort(nums);
        
        // 4. 固定第一个元素i,遍历数组(i最大为n-3,确保left和right有位置)
        for (int i = 0; i < nums.length; i++) {
            // 4.1 去重i:若当前元素与前一个元素相同,跳过(避免重复三元组)
            // 注意i>0:防止i=0时越界
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            
            // 4.2 初始化双指针:left从i+1开始(避免重复索引),right从末尾开始
            int left = i + 1;
            int right = nums.length - 1;
            
            // 4.3 双指针循环:left < right(确保两指针不重叠)
            while (left < right) {
                // 计算当前三元组的和
                int sum = nums[i] + nums[left] + nums[right];
                
                if (sum == 0) {
                    // 5. 找到符合条件的三元组,加入结果列表
                    ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
                    
                    // 5.1 去重left:跳过与当前left相同的元素(避免重复三元组)
                    // 注意left < right:防止越界
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    
                    // 5.2 去重right:跳过与当前right相同的元素
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    
                    // 5.3 移动双指针(找到一组后必须移动,避免死循环)
                    left++;
                    right--;
                } else if (sum < 0) {
                    // 6. 和偏小:左指针右移(增大数值,让总和更接近0)
                    left++;
                } else {
                    // 7. 和偏大:右指针左移(减小数值,让总和更接近0)
                    right--;
                }
            }
        }
        
        // 8. 返回所有符合条件的三元组
        return ans;
    }
}

四、实例演示(直观理解过程)

以测试用例 nums = [-1, 0, 1, 2, -1, -4] 为例,演示执行过程:

步骤 1:排序数组

排序后 nums = [-4, -1, -1, 0, 1, 2](相同元素相邻,方便去重)。

步骤 2:遍历固定元素 i

i=0(nums[i] = -4):
  • left=1(-1),right=5(2),sum = -4 + (-1) + 2 = -3 < 0 → left 右移至 2(-1);
  • sum = -4 + (-1) + 2 = -3 < 0 → left 右移至 3(0);
  • sum = -4 + 0 + 2 = -2 < 0 → left 右移至 4(1);
  • sum = -4 + 1 + 2 = -1 < 0 → left 右移至 5,此时 left 不小于 right,循环结束(无符合条件的三元组)。
i=1(nums[i] = -1):
  • 去重检查:i=1 > 0,nums [1](-1)≠ nums [0](-4)→ 不跳过;
  • left=2(-1),right=5(2),sum = -1 + (-1) + 2 = 0 → 符合条件,加入结果 [-1, -1, 2]
    • 去重 left:nums [2](-1)== nums [3](0)?否 → 不移动;
    • 去重 right:nums [5](2)== nums [4](1)?否 → 不移动;
    • 移动指针:left=3,right=4;
  • sum = -1 + 0 + 1 = 0 → 符合条件,加入结果 [-1, 0, 1]
    • 去重 left:nums [3](0)== nums [4](1)?否 → 不移动;
    • 去重 right:nums [4](1)== nums [3](0)?否 → 不移动;
    • 移动指针:left=4,right=3 → 循环结束。
i=2(nums[i] = -1):
  • 去重检查:i=2 > 0,nums [2](-1)== nums [1](-1)→ 跳过(避免重复三元组)。
i=3(nums[i] = 0):
  • left=4(1),right=5(2),sum = 0 + 1 + 2 = 3 > 0 → right 左移至 4,此时 left 不小于 right,循环结束。
i≥4:数组长度不足(left = i+1 会超过 right),循环结束。

最终结果

[[-1, -1, 2], [-1, 0, 1]],无重复三元组,符合要求。

五、关键细节:去重逻辑详解

去重是该题的核心难点,代码通过三层去重确保结果唯一:

  1. 固定元素 i 的去重if (i > 0 && nums[i] == nums[i - 1]) continue;

    • 原因:若 nums [i] 与 nums [i-1] 相同,那么以 i 为第一个元素的三元组,必然和以 i-1 为第一个元素的三元组重复(因为数组已排序,后续元素相同)。
    • 注意 i>0:防止 i=0 时访问 nums [-1] 越界。
  2. 左指针 left 的去重while (left < right && nums[left] == nums[left + 1]) left++;

    • 时机:仅在找到一个符合条件的三元组后执行(避免漏解)。
    • 原因:若 nums [left] 与 nums [left+1] 相同,移动 left 可跳过重复元素,避免同一三元组被多次加入。
    • 注意 left < right:防止 left+1 越界。
  3. 右指针 right 的去重while (left < right && nums[right] == nums[right - 1]) right--;

    • 逻辑同 left 去重,确保 right 侧无重复元素。

六、复杂度分析

  • 时间复杂度:O(n²)。排序耗时 O (n log n),外层循环遍历 i 耗时 O (n),内层双指针遍历耗时 O (n),整体由 O (n²) 主导。
  • 空间复杂度:O(log n)。主要来自排序的空间开销(Java Arrays.sort () 对基本类型使用双轴快排,空间复杂度为 O (log n)),结果列表的空间不计入(属于输出要求)。

七、总结

该解法的核心是 **"排序 + 双指针 + 分层去重"**:通过排序简化查找和去重,双指针将内层查找从 O (n²) 优化为 O (n),分层去重确保结果无重复。这种思路不仅适用于三数之和,还可迁移到 "四数之和" 等类似的 "n 数之和" 问题,是面试中高频考察的经典算法思想。

相关推荐
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——二分查找
算法·leetcode·面试·职场和发展·c/c++
lkbhua莱克瓦243 小时前
Java基础——常用算法4
java·数据结构·笔记·算法·github·排序算法·快速排序
m0_748248023 小时前
揭开 C++ vector 底层面纱:从三指针模型到手写完整实现
开发语言·c++·算法
七夜zippoe3 小时前
Ascend C流与任务管理实战:构建高效的异步计算管道
服务器·网络·算法
Greedy Alg3 小时前
LeetCode 208. 实现 Trie (前缀树)
算法
Kt&Rs3 小时前
11.5 LeetCode 题目汇总与解题思路
数据结构·算法·leetcode
还是码字踏实3 小时前
基础数据结构之数组的前缀和技巧:和为K的子数组(LeetCode 560 中等题)
算法·leetcode·前缀和·哈希字典
沙威玛_LHE7 小时前
树和二叉树
数据结构·算法
py有趣9 小时前
LeetCode算法学习之两数之和 II - 输入有序数组
学习·算法·leetcode