Skip to content

15. 三数之和 Medium

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != 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 。

解题思路

输入:一个整数数组 nums,元素可能有正有负

输出:找出所有不重复的三元组 [a, b, c],满足 a + b + c == 0

本题是典型的 排序 + 相向双指针 问题。

我们可以先对数组进行排序,然后固定第一个数 nums[i],接着在 i+1 ~ n-1 范围内使用双指针:

  1. 定义两个指针:left = i + 1right = len(nums) - 1,分别指向第二个数和第三个数的候选位置;

  2. 对于每个 nums[i]

    • 如果当前数和前一个数相同(nums[i] == nums[i - 1]),说明可能会重复三元组,跳过;
    • 使用双指针遍历 nums[i+1:right] 范围,计算三数之和;
    • 如果和小于 0,左指针右移(left += 1);
    • 如果和大于 0,右指针左移(right -= 1);
    • 如果和等于 0,加入结果集,并跳过重复的 nums[left]nums[right]
  3. 当左右指针相遇,当前 i 的处理结束,进入下一轮循环。

本质上就是固定一个数 nums[i] 之后去求剩下的两数之和等于 -num[i]

通过排序 + 去重 + 双指针,本题时间复杂度为 O(n^2),是解决「三数之和」的最优常规做法。

代码实现

python
class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()  # 先对数组排序,方便后续使用双指针

        ans = []     # 用来存放最终结果
        n = len(nums)

        for i in range(n - 2):  # 枚举第一个数的位置,注意要留出两个数给 j 和 k
            x = nums[i]

            # 去重:如果当前值和前一个一样,跳过(避免重复三元组)
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            # 剪枝1:当前最小三数之和已经大于0,后面不可能有解,直接退出
            if x + nums[i + 1] + nums[i + 2] > 0:
                break

            # 剪枝2:当前最大三数之和还小于0,不可能凑成0,跳过当前 i
            if x + nums[-1] + nums[-2] < 0:
                continue

            # 双指针从 i+1 到数组末尾开始找满足三数之和为 0 的组合
            j = i + 1
            k = n - 1
            while j < k:
                s = x + nums[j] + nums[k]  # 当前三数之和

                if s < 0:
                    j += 1  # 总和太小,左指针右移增大总和
                elif s > 0:
                    k -= 1  # 总和太大,右指针左移减小总和
                else:
                    ans.append([x, nums[j], nums[k]])  # 找到一个合法组合

                    # 跳过重复的第二个数
                    j += 1
                    while j < k and nums[j] == nums[j - 1]:
                        j += 1

                    # 跳过重复的第三个数
                    k -= 1
                    while k > j and nums[k] == nums[k + 1]:
                        k -= 1

        return ans
javascript
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
const threeSum = function(nums) {
    nums.sort((a, b) => (a - b));
    
    const ans = [];

    for (let i = 0; i < nums.length - 2; i++) {
        const x = nums[i];

        if (i > 0 && nums[i] == nums[i - 1])
            continue
        
        if ((x + nums[i + 1] + nums[i + 2]) > 0)
            break
        
        if ((x + nums.at(-1) + nums.at(-2)) < 0)
            continue
        
        let j = i + 1;
        let k = nums.length - 1;

        while (j < k) {
            const s = x + nums[j] + nums[k];

            if (s > 0) {
                k--;
            } else if (s < 0) {
                j++;
            } else {
                ans.push([x, nums[j], nums[k]]);

                j++;
                while (j < k && nums[j] == nums[j - 1]) {
                    j++;
                }

                k--;
                while (j < k && nums[k] == nums[k + 1]) {
                    k--;
                }
            }
        }
    }
    return ans;
};

复杂度分析

时间复杂度:O(n^2)

空间复杂度:O(1)

链接

15 国际版

15 中文版