LeetCode 热题 100——双指针——三数之和

三数之和

题目描述

给你一个整数数组 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 。

提示:

3 <= nums.length <= 3000

-105 <= nums[i] <= 105

求解

(1) 暴力

有五个用例超出时间限制

javascript 复制代码
var threeSum = function(nums) {
    // 先暴力,总得先解出来
    nums.sort((a, b) => a - b)
    let set = new Set()
    for (let i = 0; i < nums.length - 2; i++) {
        for(let j = i + 1; j < nums.length - 1; j++) {
            for(let k = j + 1; k < nums.length; k++) {
                if (nums[i] + nums[j] + nums[k] === 0) {
                    // Set 本身是基于引用地址去重的,而数组是引用类型,所以 set.add([nums[i], nums[j], nums[k]]) 无法正确去重
                    // 将 数组转为字符串,并使用逗号隔开
                    let item = [nums[i], nums[j], nums[k]].join(',')
                    set.add(item)
                }
            }
        }
    }
    // 返回的时候需要将 字符串 按照逗号 分割并转为数字类型
    let ans = Array.from(set).map(str => str.split(',').map(Number))
    return ans
};

不足:对js一些语法用的还是不够熟练;还是要多学一些优化策略。

三重循环暴力解法虽然思路清晰,但时间复杂度是 O(n3)O (n^3)O(n3),在数据量大的时候会超时。这里有一个基于 "双指针" 的优化思路,可以将时间复杂度降低到(n2)(n^2)(n2),并且天然地避免了使用 Set 去重,效率更高。

(2)双指针

先做一个两数之和的题目,如下:

两数之和 II - 输入有序数组

  • 题目描述:

    给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

    函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

    假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

    示例 1:

    输入:numbers = [1,2,4,6,10], target = 8

    输出:[1,3]

    解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。

    示例 2:

    输入:numbers = [2,3,4], target = 6

    输出:[0,2]

    示例 3:

    输入:numbers = [-1,0], target = -1

    输出:[0,1]

    提示:

    2 <= numbers.length <= 3 * 104

    -1000 <= numbers[i] <= 1000

    numbers 按 非递减顺序 排列

    -1000 <= target <= 1000

    仅存在一个有效答案

  • 求解:

    这个题目 首先已经排好序了,关键就在于:双指针,分别指向最小值和最大值;最大的和最小的相加,比target大的话说明 最大的太大了,应该往前移动;比target小的话,说明最小的太小了,需要往后移动

    javascript 复制代码
    var twoSum = function(nums, target) {
        let left = 0
            right = nums.length - 1
        while (left < right) {
            let sum = nums[left] + nums[right]
            if (sum === target) return [left, right]
            if (sum < target) {
                // 最大的加最小的都比target小。最小的应该 右移
                left++
            } else {
                // 最大的加最小的 比target大,最大的应该 左移
                right--
            }
        }
        return []
    };

那么回到三数之和这道题目中,首先进行排序,然后 先找一个,再用双指针找其他两个。思路如下:

  • 先对数组进行升序排序。
  • 固定第一个数 nums[i]。
  • 用两个指针 left 和 right 分别指向 i 后面的两端(left = i + 1, right = nums.length - 1)。
  • 计算三数之和 sum = nums[i] + nums[left] + nums[right]。
  • 如果 sum === 0,则找到一个解。为了避免重复,需要同时移动 left 和 right 指针,并跳过所有相同的元素。
  • 如果 sum < 0,说明需要更大的数,将 left 指针右移。
  • 如果 sum > 0,说明需要更小的数,将 right 指针左移。

代码如下:

javascript 复制代码
var threeSum = function(nums) {
    // 排序
    nums.sort((a, b) => a - b)
    let ans = []
    // 遍历
    for (let i = 0; i < nums.length - 2; i++) {
        // 固定i,找j、k
        if (i > 0 && nums[i] === nums[i - 1]) continue
        if (nums[i] > 0) break
        let j = i + 1
            k = nums.length - 1
        while(j < k) {
            if (nums[j] + nums[k] + nums[i] === 0) {
                // 找到符合条件的
                ans.push([nums[i], nums[j], nums[k]])
                // 跳过所有相同的left和right,避免重复结果
                while (j < k && nums[j] === nums[j + 1]) j++
                while (j < k && nums[k] === nums[k - 1]) k--
                j++
                k--
            } else if (nums[j] + nums[k] < -nums[i]) {
                // -nums[i] 相当于是target
                j++
            } else {
                k--
            }
        }
    }
    return ans
};
相关推荐
高山上有一只小老虎2 小时前
等差数列前n项的和
java·算法
sin_hielo2 小时前
leetcode 2536
数据结构·算法·leetcode
flashlight_hi3 小时前
LeetCode 分类刷题:203. 移除链表元素
算法·leetcode·链表
py有趣3 小时前
LeetCode算法学习之数组中的第K个最大元素
学习·算法·leetcode
吗~喽3 小时前
【LeetCode】将 x 减到 0 的最小操作数
算法·leetcode
牛客企业服务3 小时前
2025年AI面试防作弊指南:技术笔试如何识别异常行为
人工智能·面试·职场和发展
what_20183 小时前
list集合使用
数据结构·算法·list
hetao17338373 小时前
2025-11-13~14 hetao1733837的刷题记录
c++·算法
hansang_IR4 小时前
【题解】洛谷 P2476 [SCOI2008] 着色方案 [记搜]
c++·算法·记忆化搜索