LeetCode 15: 三数之和(3Sum)

📝 问题描述

给定一个整数数组 nums,找出所有和为 0 且不重复的三元组 [nums[i], nums[j], nums[k]],其中 i != j != k

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

🔍 解题思路

这个问题的关键挑战在于:

  1. 需要找到所有符合条件的三元组

  2. 结果不能包含重复三元组

  3. 数组可能很大(长度达3000)

最直接的方法是使用三重循环,但时间复杂度为 O(n³),效率较低。我们可以通过排序和双指针技术将复杂度降至 O(n²)。

🧮 算法步骤

  1. 排序:首先对数组进行排序,这样相同的元素会相邻,便于去重

  2. 固定第一个数:遍历排序后的数组,固定第一个数 nums[i]

  3. 双指针:使用两个指针 jk,分别从 i+1 和数组末尾向中间移动

  4. 计算三数之和

    • 如果和为0,记录结果并移动双指针

    • 如果和小于0,左指针右移(增加和)

    • 如果和大于0,右指针左移(减小和)

  5. 去重处理:对于所有指针,当遇到相同元素时,跳过以避免重复结果

💻 代码实现

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int i = 0;
        List<List<Integer>> result = new ArrayList<>();
​
        // 先对数组进行排序
        Arrays.sort(nums);
​
        while (i < nums.length - 2) {
            // 如果第一个数大于0,因为数组已排序,后面不可能有三数之和等于0的情况
            if (nums[i] > 0) break;
          
            int j = i + 1;          // 左指针
            int k = nums.length - 1; // 右指针
​
            while (j < k) {
                int sum = nums[i] + nums[j] + nums[k];
​
                if (sum == 0) {
                    // 找到一组解
                    List<Integer> triplet = new ArrayList<>();
                    triplet.add(nums[i]);
                    triplet.add(nums[j]);
                    triplet.add(nums[k]);
                    result.add(triplet);
​
                    // 跳过重复元素
                    while (j < k && nums[j] == nums[j + 1]) j++;
                    while (j < k && nums[k] == nums[k - 1]) k--;
​
                    // 继续查找其他可能的解
                    j++;
                    k--;
                } else if (sum < 0) {
                    // 和太小,左指针右移
                    j++;
                } else {
                    // 和太大,右指针左移
                    k--;
                }
            }
​
            // 跳过重复的第一个数
            while (i < nums.length - 2 && nums[i] == nums[i + 1]) i++;
            i++;
        }
​
        return result;
    }
}

⚙️ 代码逻辑分解

1. 初始化和排序

int i = 0;
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
  • 创建结果列表和起始索引

  • 对数组排序,为后续操作做准备

2. 外层循环:固定第一个数

while (i < nums.length - 2) {
    // 循环体
  
    // 跳过重复的第一个数
    while (i < nums.length - 2 && nums[i] == nums[i + 1]) i++;
    i++;
}
  • 遍历数组,固定三元组的第一个元素

  • 循环结束后跳过重复元素,避免产生重复结果

3. 内层循环:双指针查找

int j = i + 1;
int k = nums.length - 1;
​
while (j < k) {
    int sum = nums[i] + nums[j] + nums[k];
  
    // 处理各种情况
}
  • 使用双指针技术,在区间 [i+1, n-1] 内寻找符合条件的两个数

4. 三种和的情况处理

if (sum == 0) {
    // 添加结果并跳过重复元素
    // ...
    j++;
    k--;
} else if (sum < 0) {
    j++;
} else {
    k--;
}
  • 根据三数之和与0的比较结果,决定如何移动指针

🔄 去重逻辑

代码中有三处去重处理:

  1. 找到解后,跳过左指针的重复元素:while (j < k && nums[j] == nums[j + 1]) j++;

  2. 找到解后,跳过右指针的重复元素:while (j < k && nums[k] == nums[k - 1]) k--;

  3. 每轮外循环结束后,跳过第一个数的重复元素:while (i < nums.length - 2 && nums[i] == nums[i + 1]) i++;

⏱️ 复杂度分析

  • 时间复杂度:O(n²)

    • 排序需要 O(n log n)

    • 双层循环需要 O(n²)

    • 总体为 O(n²)

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

    • 仅使用常数额外空间

🌟 关键技巧

  1. 排序 + 双指针:这是解决此类问题的常用技巧

  2. 提前结束条件:当 nums[i] > 0 时可提前终止循环

  3. 去重处理:在各个指针移动时避免处理重复元素

📊 示例演示

nums = [-1,0,1,2,-1,-4] 为例:

  1. 排序后:[-4,-1,-1,0,1,2]

  2. 固定第一个数 -4

    • 双指针搜索 [-1,-1,0,1,2],无解

  3. 固定第一个数 -1

    • 找到 [-1,0,1],和为0,记录

    • 找到 [-1,-1,2],和为0,记录

  4. 固定第一个数 -1(重复,跳过)

  5. 固定第一个数 0

    • 无新解

  6. 返回结果:[[-1,0,1],[-1,-1,2]]

通过排序和双指针技术,我们成功高效地解决了三数之和问题,找出了所有不重复的三元组。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值