hot100 三数之和(15)

📅 2026/6/26 7:26:08
hot100 三数之和(15)
一、 算法核心思想“三数之和”最大的难点不在于找出组合而在于如何高效地去除重复解即答案中不能包含重复的三元组。该算法通过以下三个步骤精妙地解决了这个问题预处理排序首先对整个数组进行升序排序。排序的作用有两个允许我们使用双指针从两端向中间靠拢单调性。将相同的数字聚集在一起为后续的去重操作打下基础。固定基准数外层循环遍历数组以当前元素nums[i]作为三元组的第一个数。双指针扫描内层循环在nums[i]右侧的剩余区间[i 1, n - 1]内设置左指针left和右指针right。如果三个数之和等于 0记录结果。如果和小于 0说明数据太小左指针右移left。如果和大于 0说明数据太大右指针左移right--。二、 代码逐行拆解与去重细节以下是该解法的源码及深度注释重点解析了算法中的三次去重逻辑。class Solution { public ListListInteger threeSum(int[] nums) { // 1. 关键步骤先对数组进行升序排序 Arrays.sort(nums); ListListInteger ans new ArrayList(); int n nums.length; // 2. 外层循环固定三元组的第一个元素 nums[i] for (int i 0; i n - 2; i) { // 【第一处去重】如果当前元素和上一个元素相同直接跳过 // 思考为什么是和 nums[i-1] 比而不是和 nums[i1] 比 // 答因为 nums[i-1] 已经作为第一个数遍历过了所有包含它的组合都已找完。 // 如果和 nums[i1] 比会把类似 [-1, -1, 2] 这种合法的包含重复元素的三元组错误地漏掉。 if (i 1 nums[i] nums[i - 1]) { continue; } // 3. 初始化双指针 int left i 1; // 两个指针只在固定的基准数右侧移动 int right n - 1; int target -nums[i]; // 我们需要满足 nums[left] nums[right] target // 4. 内层循环双指针向中间靠拢 while (left right) { int sum nums[left] nums[right]; if (sum target) { // 找到满足条件的三元组存入结果集 ans.add(List.of(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--; } else if (sum target) { // 和太小了左指针右移以增大和 left; } else { // 和太大了右指针左移以减小和 right--; } } } return ans; } }三、 面试官常问去重逻辑深度剖析这道题在 CSDN 或面试中最有价值的部分就是去重细节。1. 为什么外层去重要用nums[i] nums[i-1]如果我们写成nums[i] nums[i1]在面对输入[-1, -1, 2]时当i 0nums[0] -1时算法发现nums[0] nums[1]直接continue跳过。这样就会导致[-1, -1, 2]这个唯一正确的解被丢弃。正确逻辑我们应该允许同一个三元组内部包含重复数字如nums[0]和nums[1]但不能允许两个不同三元组的首元素在不同轮次里扮演相同的角色。因此通过与历史过去的nums[i-1]对比能确保“用过即丢弃”。2. 内层双指针找到解后为什么要一边执行while去重最后还要执行left和right--当sum target时如果不去重left和right仅仅各移动一步如果移动到的新位置数字和刚才一模一样就会产生一模一样的重复三元组。内部的while循环是负责把指针推到重复数字的最后一个位置。随后的left和right--才是真正让指针跨越到全新数字的关键一步。四、 复杂度分析1. 时间复杂度数组排序Arrays.sort(nums)消耗O(n log n)时间。双层循环扫描外层循环执行了n次。内层双指针left和right在每一轮外层循环中合起来最多从两端走到中间即最多移动n次。嵌套循环的时间复杂度为O(n * n) O(n^2)。总时间复杂度O(n log n) O(n^2) O(n^2)。相比于暴力三层循环的O(n^3)这是一个质的飞跃。2. 空间复杂度辅助空间在算法执行过程中除了存储答案的ans之外我们只使用了left、right、sum、target等几个常数级别的变量它们占用O(1)的空间。排序空间Java 中Arrays.sort()对对象或基元类型的排序如快速排序、TimSort会消耗O(log n)到O(n)的隐式栈空间。总空间复杂度忽略输出结果集算法的执行空间复杂度为O(log n)主要由排序算法的栈空间决定。