LeetCode - 15 三数之和

目录

题目来源

题目描述

示例

提示

题目解析

算法源码


题目来源

15. 三数之和 - 力扣(LeetCode)

题目描述

给你一个整数数组 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
  • -10^5 <= nums[i] <= 10^5

题目解析

本题需要从nums数组中找出所有和为0的三元组,且找出的三元组不能重复。

本题可以分解为两部分逻辑:

  1. 找到和为0的三元组
  2. 三元组去重

找和为0的三元组,最简单的方法就是三重for暴力枚举,时间复杂度O(n^3),n = nums.length(),结合备注的nums长度,可以得出暴力法会超时。

因此,我们需要一种更优的算法。

当前最优算法思路为:

首先,将nums数组进行升序,

然后,利用三个指针 i, l, r 来选择三个数,如果i, l,r指针指向的三数之和为0,则找到一个符合要求的三元组。

三指针指向的元素值的大小关系:nums[i] <= nums[l] <= nums[r]

上面三指针的运动逻辑其实可以概括,

首先确定三元组的最小值nums[i],然后找到一个与之匹配的nums[l],nums[r]

这样的话,就将三指针运动 转为了 双指针运动。

我们通过下面 示例1 图示来理解运动过程:

nums = [-1,0,1,2,-1,-4] 经过升序后,变为nums = [-4,-1,-1,0,1,2]

如上图,我们分别找了:

  • 三元组中最小值为-4,即 i 固定指向 0
  • 三元组中最小值为-1,即 i 固定指向 1
  • 三元组中最小值为-1,即 i 固定指向 2
  • 三元组中最小值为0,即 i 固定指向 3

的三元组情况。而针对上面情况,初始时l,r指针固定为:

  • l = i + 1
  • r = nums.length - 1

由于nums已经升序,因此,如果i, l, r三元组之和sum

  • sum > 0,则说明三元组之和大了,我们应该减小它,此时
  1. i 指针是固定的,因此不能移动 i 指针
  2. l 指针只能向右移动,因为 l 不能比 i 小,但是 l 指针右移只会增大三元组之和,因此不能移动 l 指针
  3. r 指针只有向左移动,而 r 指针向左移动的话,会导致nums[r]减少,因此三元组之和也会减少,所以我们左移 r 指针,即r--,可以减少三元组之和
  • sum < 0,则说明三元组之和小了,我们应该增大它,此时我们应该右移 l 指针,即 l++,来增大三元组之和
  • sum == 0,则说明当前i, l, r指向的三个数就是一个和为0的三元组

上面就是找和为0的三元组的逻辑。


下面就是去重逻辑的实现了

我们通过示例一可以发现,三指针运动过程中,产生了重复的三元组,如下图所示

那么如何才能去重呢?

最简单的方法,其实就是将所有和为0的三元组求出后,对比去重,但是效率非常低,不推荐。

更高效的去重策略是,发现规律,通过上面图示,我们可以发现:

重复的两个三元组,如果基于后面一个来看的话,那么变化的只有 i 指针,且nums[i] == nums[i-1],而l,r位置未发生改变。

如果我们把nums[i]和nums[i-1]看出一个整体的话,那么这两个重复二元组就是同一个。

且他们对于的l,r指针的运动逻辑也是一致的。因此后续会产生重复的二元组。

因此,我们可得去重结论,如果 num[i] == nums[i-1],则后续的l,r指针运动就可以不做了,因为会产生重复的二元组。


如果上面的 i 指针会产生重复二元组外,对于 l, r 指针同样会产生重复二元组。

比如下面例子:

上面例子中,产生了多个重复的三元组

但是实际上,

如果 nums[l] == nums[l+1],则可以直接 l++ 

如果 nums[r] == nums[r-1],则可以直接 r--

即运动过程如下:

即 nums[l] == nums[l+1] 的话,则 i, l, r 其实和 i, l+1,r 三元组是重复的,因此,我们可以直接跳过l+1的三元组,选择l++

    nums[r] == nums[r-1] 的话,则i ,l , r其实和i, l, r-1 三元组是重复的,因此,我们可以直接跳过r-1的三元组,选择r-- 


另外本题还有一个剪枝优化,

如果 nums[i] > 0的话,由于nums[i]是三元组中最小值,因此nums[i] > 0的话,则nums[l],nums[r]必然也大于0,三元组之和也大于0。

并且nums已经升序,因此nums[i+1]>0也是必然的,即nums[i+1]对应的三元组也是大于0的。

因此如果nums[i] > 0了,则可以直接break掉整个三指针运动。

JS算法源码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  const ans = [];

  nums.sort((a, b) => a - b);

  for (let i = 0; i < nums.length - 2; i++) {
    // 剪枝
    if (nums[i] > 0) break;

    // 去重
    if (i > 0 && nums[i] == nums[i - 1]) continue;

    let l = i + 1;
    let r = nums.length - 1;

    while (l < r) {
      const sum = nums[i] + nums[l] + nums[r];

      if (sum > 0) {
        r--;
      } else if (sum < 0) {
        l++;
      } else {
        ans.push([nums[i], nums[l], nums[r]]);

        // 去重
        while (l + 1 < r && nums[l] == nums[l + 1]) l++;
        // 去重
        while (r - 1 > l && nums[r] == nums[r - 1]) r--;
        l++;
        r--;
      }
    }
  }

  return ans;
};

三数之和 - 提交记录 - 力扣(LeetCode)

Java算法源码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

class Solution {
  public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();

    Arrays.sort(nums);

    for (int i = 0; i < nums.length - 2; i++) {
      // 剪枝优化
      if (nums[i] > 0) break;

      // 去重
      if (i > 0 && nums[i] == nums[i - 1]) continue;

      int l = i + 1;
      int r = nums.length - 1;

      while (l < r) {
        int sum = nums[i] + nums[l] + nums[r];

        if (sum > 0) {
          r--;
        } else if (sum < 0) {
          l++;
        } else {
          ArrayList<Integer> tmp = new ArrayList<>();
          Collections.addAll(tmp, nums[i], nums[l], nums[r]);
          ans.add(tmp);

          // 去重
          while (l + 1 < r && nums[l] == nums[l + 1]) l++;
          // 去重
          while (r - 1 > l && nums[r] == nums[r - 1]) r--;

          l++;
          r--;
        }
      }
    }

    return ans;
  }
}

三数之和 - 提交记录 - 力扣(LeetCode)

Python算法源码

class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        ans = []

        nums.sort()

        for i in range(len(nums) - 2):
            # 剪枝
            if nums[i] > 0:
                break

            # 去重            
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            l = i + 1
            r = len(nums) - 1

            while l < r:
                total = nums[i] + nums[l] + nums[r]

                if total > 0:
                    r -= 1
                elif total < 0:
                    l += 1
                else:
                    ans.append([nums[i], nums[l], nums[r]])

                    # 去重
                    while l + 1 < r and nums[l] == nums[l + 1]:
                        l += 1

                    # 去重
                    while r - 1 > l and nums[r] == nums[r - 1]:
                        r -= 1

                    l += 1
                    r -= 1

        return ans

三数之和 - 提交记录 - 力扣(LeetCode)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/27323.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cmake工具的简单使用

引言 本篇文章讲述如何简单的使用cmake工具构建一个项目&#xff0c;帮助入门的c新手学会如何使用cmake. 我们在Clion新创建一个项目时&#xff0c;会发现&#xff0c;除了main.cpp文件之外&#xff0c;还存在一个build-debug目录和一个CMakelists.txt文件&#xff0c;如图: …

群晖 NAS 外网访问设置 - 腾讯 DNSPod

目录 ​编辑 一、使用DNSPod&#xff0c;实现DDNS&#xff08;动态域名&#xff09; 二、公共概念厘清 三、腾讯DNSPod上详细设置步骤 1. 打开DNSPod.cn网站并登录 2. 登录成功后&#xff0c;选择【我的域名】-> 【添加域名】 3. 添加群晖NAS需要二级域名&#xff08…

安装 Kafka

文章目录 1.选择操作系统2.配置 Java 环境3.安装 ZooKeeper4.安装 broker&#xff08;1&#xff09;安装 broker&#xff08;2&#xff09;验证是否安装正确 5.配置 broker&#xff08;1&#xff09;常规配置&#xff08;2&#xff09;主题的默认配置 6.配置 Kafka 集群&#x…

一种新型智能优化算法—鼠群优化(RSO)算法

目录 一、RSO理论基础 二、RSO数学模型 2.1 追逐猎物 2.2 攻击猎物 三、RSO流程图 四、运行结果 鼠群优化(Rat Swarm Optimizer&#xff0c;RSO)算法是由Dhiman G等人于2020年提出&#xff0c;主要启发于老鼠追逐和攻击猎物的种群行为。该优化算法具有结构简单&#xf…

【Spring源码解读三】IoC容器之AnnotationConfigApplication的refresh()刷新方法其二

invokeBeanFactoryPostProcessors() PriorityOrdered接口 Ordered接口 invokeBeanDefinitionRegistryPostProcessors() registerBeanPostProcessors() getBeanNamesForType() initMessageSource() initApplicationEventMulticaster() onRefresh() registerListeners()…

go语言学习——9

文章目录 goroutine概念goroutine调度模型 channelchannel介绍定义/声明channelchannel的关闭channel遍历channel其他细节 goroutine 前言&#xff1a;统计1~90000000数字中&#xff0c;哪些是素数&#xff1f; 使用循环&#xff0c;很慢使用并发或者并行的方式&#xff0c;将任…

【保姆级】如何创建一个Vue工程

如何创建一个Vue工程 文章目录 如何创建一个Vue工程1、下载安装Node.js2、配置环境变量3、npm 安装淘宝镜像4、安装Vue CliVue 安装失败原因 5、在线创建工程创建工程启动服务启动报错停止服务重启服务 1、下载安装Node.js Node.js是一个js运行环境&#xff0c;Vue工程需要建立…

机器学习实战六步法之数据收集方法(四)

要落地一个机器学习的项目&#xff0c;是有章可循的&#xff0c;通过这六个步骤&#xff0c;小白也能搞定机器学习。 看我闪电六连鞭&#xff01;&#x1f923; 数据收集 数据是机器学习的基础&#xff0c;没有数据一切都是空谈&#xff01;数据集的数据量和数据的质量往往决…

有哪些比较好的游戏图标推荐

游戏图标设计在游戏UI中占有非常重要的地位。例如&#xff0c;当我们看到一个游戏的启动图标时&#xff0c;很容易区分它是哪个游戏。设计游戏图标不仅是一个图形&#xff0c;也是一个标志。 本文将通过各种游戏图标设计素材分享游戏图标的类别和设计游戏图标的思考。 1. 游戏…

HTTPS 协议

哥几个来学 HTTPS 协议 啦 ~~ 目录 &#x1f332;一、HTTPS 是什么&#xff1f; &#x1f333;二、何为 “加密” &#x1f334;三、HTTPS 的工作过程 &#x1f366;1. 引入对称加密 &#x1f367;2. 引入非对称加密 &#x1f368;3.引入证书 &#x1f332;一、HTTPS 是什…

如何学习和提升使用编程语言的能力? - 易智编译EaseEditing

学习编程语言并提升编程能力需要一定的学习方法和实践。以下是一些方法可以帮助你提升编程语言能力&#xff1a; 学习基本语法&#xff1a; 了解编程语言的基本语法和关键概念。可以通过阅读官方文档、教程、书籍或在线资源来学习。 编写代码&#xff1a; 编写实际的代码是提…

【深度学习】日常笔记

一开始感觉学习方向有点飘忽不定&#xff0c;后面查找资料和思考&#xff0c;发现其实图神经网络、异构图、推荐系统这三者的概念其实是相通&#xff0c;紧密联系的。推荐系统是指根据用户历史行为和偏好&#xff0c;为用户提供个性化的商品或服务推荐。而在推荐系统中&#xf…

用GANs来做数据增强

适用于只有很少样本的情况。 即使是不完美的合成数据也可以提高分类器的性能。 生成对抗网络(Generative adversarial networks&#xff0c;简称GANs)由Ian Goodfellow于2014年推出&#xff0c;近年来成为机器学习研究中非常活跃的话题。GAN是一种无监督生成模型&#xff0c;它…

基于word文档,使用Python输出关键词和词频,并将关键词的词性也标注出来

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注 回复“书籍”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 移船相近邀相见&#xff0c;添酒回灯重开宴。 大家好&#xff0c;我是Python进阶者。 一、前言 前几天在有个粉丝问了个问题&#xff0c;大概意思是这样…

一阶电路和二阶电路的时域分析(1)——“电路分析”

小雅兰期末加油冲冲冲&#xff01;&#xff01;&#xff01; 动态电路的方程及其初始条件 动态电路&#xff0c;物理学名词&#xff0c;是指含有储能元件L、C的电路&#xff0c;动态电路方程的阶数通常等于电路中动态元件的个数。 动态电路是指含有储能元件的电路。当动态电路状…

【Java】Java核心要点总结:58

文章目录 1. java中 怎么确保一个集合不能被修改2. 队列和栈是什么 有什么区别3. Java8开始的ConcurrentHashMap为什么舍弃了分段锁4. ConcurrentHashMap 和 Hashtable有什么区别5. ReadWriteLock和StampeLock 1. java中 怎么确保一个集合不能被修改 Java 中可以使用 Collectio…

计算机网络(数据链路层,复习自用)

数据链路层 数据链路层功能概述封装成帧与透明传输差错编码&#xff08;检错编码&#xff09;差错编码&#xff08;纠错编码&#xff09;流量控制与可靠传输机制停止-等待协议后退N帧协议&#xff08;GBN&#xff09;选择重传协议&#xff08;Selective Repeat&#xff09; 信道…

并行事务会引发的三个问题

并行事务是指同时运行多个事务&#xff0c;每个事务独立地执行&#xff0c;并且不会相互影响。在数据库管理系统中&#xff0c;当多个用户同时对同一个数据集进行读取或者写入的时候&#xff0c;使用并行事务可以提高系统的吞吐量和响应时间。同时&#xff0c;由于并行事务可以…

【MySQL 数据库】7、SQL 优化

目录 一、插入数据优化(1) insert 语句① 批量插入数据② 手动控制事务③ 主键顺序插入&#xff0c;性能要高于乱序插入 (2) load 大批量插入数据【☆❀ 二、主键优化(1) 数据组织形式(2) 页分裂(3) 页合并(4) 主键设计原则 三、orber by 优化四、group by 优化五、limit 优化&…

基于前推回代法的连续潮流计算研究【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…