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
};
相关推荐
gihigo19981 天前
matlab 基于瑞利衰落信道的误码率分析
算法
foxsen_xia1 天前
go(基础06)——结构体取代类
开发语言·算法·golang
foxsen_xia1 天前
go(基础08)——多态
算法·golang
leoufung1 天前
用三色 DFS 拿下 Course Schedule(LeetCode 207)
算法·leetcode·深度优先
im_AMBER1 天前
算法笔记 18 二分查找
数据结构·笔记·学习·算法
C雨后彩虹1 天前
机器人活动区域
java·数据结构·算法·华为·面试
MarkHD1 天前
车辆TBOX科普 第53次 三位一体智能车辆监控:电子围栏算法、驾驶行为分析与故障诊断逻辑深度解析
算法
苏小瀚1 天前
[算法]---路径问题
数据结构·算法·leetcode
月明长歌1 天前
【码道初阶】一道经典简单题:多数元素(LeetCode 169)|Boyer-Moore 投票算法详解
算法·leetcode·职场和发展
wadesir1 天前
C语言模块化设计入门指南(从零开始构建清晰可维护的C程序)
c语言·开发语言·算法