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
};
相关推荐
我命由我1234515 小时前
Photoshop - Photoshop 工具栏(43)标尺工具
学习·ui·职场和发展·求职招聘·职场发展·学习方法·photoshop
夏幻灵15 小时前
为什么要配置环境变量?
笔记·算法
铭哥的编程日记15 小时前
Manacher算法解决所有回文串问题 (覆盖所有题型)
算法
LYFlied15 小时前
【每日算法】LeetCode 300. 最长递增子序列
前端·数据结构·算法·leetcode·职场和发展
ohnoooo915 小时前
251225 算法2 期末练习
算法·动态规划·图论
车队老哥记录生活16 小时前
强化学习 RL 基础 3:随机近似方法 | 梯度下降
人工智能·算法·机器学习·强化学习
闲看云起16 小时前
LeetCode-day2:字母异位词分组分析
算法·leetcode·职场和发展
熬夜敲代码的小N16 小时前
2026 职场生存白皮书:Gemini Pro 实战使用指南
人工智能·python·ai·职场和发展
NAGNIP16 小时前
Hugging Face 200页的大模型训练实录
人工智能·算法
Swift社区16 小时前
LeetCode 457 - 环形数组是否存在循环
算法·leetcode·职场和发展