【LeetCode刷题笔记(6-1)】【Python】【三数之和】【哈希表】【中等】

文章目录

  • 三数之和
    • 题目描述
    • 示例
      • 示例1
      • 示例2
      • 示例3
    • 提示
    • 解决方案1:【三层遍历查找】
    • 解决方案2:【哈希表】+【两层遍历】
  • 结束语

三数之和

三数之和

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0

请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例

示例1

  • 输入:nums = [-1,0,1,2,-1,-4]
  • 输出:[[-1,-1,2],[-1,0,1]]

示例2

  • 输入:nums = [0,1,1]
  • 输出:[]
  • 解释:唯一可能的三元组和不为 0 。

示例3

  • 输入:nums = [0,0,0]
  • 输出:[[0,0,0]]
  • 解释:唯一可能的三元组和为 0 。

提示

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

解决方案1:【三层遍历查找】

对于解决【三数之和】这个问题,一种直观的解法是三层循环枚举所有可能的三元组,然后判断它们的和是否为零,但是这样的时间复杂度是 O(n3),对于较大的数组来说是不可接受的。

解决方案2:【哈希表】+【两层遍历】

在探讨【三数之和】这一算法题之前,我相信许多读者已经对【两数之和】有所涉猎。在我们深入理解题目要求时,我们明确了解决【两数之和】问题的核心是【如何高效查找目标值】。而【哈希表】以其迅速的查找速度脱颖而出,成为解决此类问题的得力助手。

现在,摆在我们面前的是【三数之和】问题,它与【两数之和】有着诸多相似之处。因此,我们很自然地会联想到运用【哈希表】来助力解决。这种思维跳跃不仅体现了我们对已知知识的灵活运用,更展示了我们在面对新问题时的敏捷思维。

与【三层遍历】相比,【哈希表】是一种以空间换时间的解决方案。首先,数组nums中可能存在大量值相同但索引不同的元素,如下所示:

nums1 = [0] * 10 + [1] * 10 + [-1] * 10
print(nums1)
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]

对于这个问题而言,这些大量重复的元素显然是冗余的。我们的核心目标是在数组中找到N个不重复的三元组,在这些三元组中,元素之和为0 ==> 除了[0, 0, 0]这种特殊情况,其它任何数都不可能在一个三元组中重复三次(和不可能为0)。那么nums1实际上等价于数组[0, 0, 0, 1, 1, -1, -1] ==> 这意味着我们可以先对原数组nums先进行【去重】操作,形成一个新数组new_nums,在新数组中,除了0可以重复三次,其它值至多重复两次。

原数组【去重】的重要意义:某些情况可以显著降低整体算法的时间复杂度。比如说上面代码里的nums1,原来有30个元素,那么两层循环遍历的时间复杂度是O(n2),n=30; 如果对nums1进行去重,去重后的新数组实际上只包含7个元素,两层循环遍历的时间复杂度从O(302)将至O(72)!

原数组【去重】代码如下

# 创建哈希表,记录去重后的新数组元素,方便后续遍历查找目标值。
hash_map = {} # 哈希表的键为新数组元素值,值为元素值在新数组的新下标num_idx
num_idx = 0 # 新数组的下标初始化为0
new_nums = [] # 用新的数组记录去重后的数组

# 遍历原数组
for idx, num in enumerate(nums): 
	# 当前元素不在哈希表中
    if num not in hash_map:
        hash_map[num] = [num_idx] # 创建新的键-值对,记录该元素
        # 记录去重后仍保留的数组元素
        new_nums.append(num) 
        num_idx += 1 
    else: # 当前元素已在哈希表中
    	# 如果该元素已在哈希表中记录两次
        if len(hash_map[num]) == 2:
            if num == 0: # 除非该元素是0,否则不再记录
                hash_map[num].append(num_idx)
                new_nums.append(num)
                num_idx += 1
        # 如果该元素已在哈希表中记录一次
        elif len(hash_map[num]) == 1:
        	# 在哈希表中再次记录该元素
            hash_map[num].append(num_idx)
            # 新数组同时记录该元素
            new_nums.append(num)
            num_idx += 1
        # 其它情况不做任何处理
        else:
            pass

当有了哈希表hash_map后,便可以通过【两层遍历】在哈希表中查找目标值来得到有效的三元组。

算法步骤

  1. 第一层循环:遍历数组元素nums[i] —> i从0–>n,n为新数组的元素个数,执行步骤(2);
  2. 第二层循环:遍历数组元素nums[j] —> ji+1–>n,n为新数组的元素个数,执行步骤(3);
  3. nums[i] + nums[j]相反数-(nums[i] + nums[j])不存在于哈希表hash_map中,则返回步骤(1); 若存在,说明找到可能正确的三元组,执行步骤(4);
  4. 遍历哈希表中-(nums[i] + nums[j])】对应的每个索引k —> k至多有三个不同的值(分别是三个0元素所对应的索引),执行步骤(5);
  5. k=i或者k=j, 说明这个元素三元组将出现重复的元素(同一索引),不符合题意,返回步骤(1)<— 第一次去重:避免在元素三元组中出现同一元素(同索引的元素) ;若k!=i and k!=j,说明当前的三个索引i,j,k互不相同,可能是正确的三元组,执行步骤(6);
    细节】:尽管三个索引i,j,k互不相同,但仍然不能保证由索引三元组对应的元素三元组不会在结果列表中出现重复 <— 不同的索引三元组可能对应同一种元素三元组;
  6. 参考字母异位词分组的解决方案,对当前三个索引所生成的结果列表[new_nums[k], new_nums[i], new_nums[j]]进行【排序】。因为一旦出现重复的三元组结果(如[1, 0, -1] 和[0, 1, -1]),它们虽然顺序不同,但排序结果一定是相同
    检查排序后的元素三元组sorted_result是否存在于哈希表is_used_results中,若已存在,说明出现了重复的元素三元组,不符合题意,返回步骤(1)<— 第二次去重:避免出现重复的元素三元组,尽管三个索引i,j,k互不相同 ;若不存在,说明这个元素三元组是无重复的,执行步骤(7);
  7. 将元素三元组保存于结果列表result_list中,重复执行步骤1-7,直到循环结束。

细节】既然已经有一个结果列表result_list记录元素三元组,为什么不直接判断排序后的元素三元组sorted_result是否存在于结果列表result_list中,而是重新创建一个哈希表is_used_results来协助判断?

答:因为哈希表的查找速度非常快!!!,如果在列表中查找,可能会超时

完整代码如下

class Solution:  
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 创建哈希表,记录去重后的新数组元素,方便后续遍历查找目标值。
        hash_map = {} # 哈希表的键为新数组元素值,值为元素值在新数组的新下标num_idx
        num_idx = 0 # 新数组的下标初始化为0
        new_nums = [] # 用新的数组记录去重后的数组

        # 遍历原数组
        for idx, num in enumerate(nums): 
            # 当前元素不在哈希表中
            if num not in hash_map:
                hash_map[num] = [num_idx] # 创建新的键-值对,记录该元素
                # 记录去重后仍保留的数组元素
                new_nums.append(num) 
                num_idx += 1 
            else: # 当前元素已在哈希表中
                # 如果该元素已在哈希表中记录两次
                if len(hash_map[num]) == 2:
                    if num == 0: # 除非该元素是0,否则不再记录
                        hash_map[num].append(num_idx)
                        new_nums.append(num)
                        num_idx += 1
                # 如果该元素已在哈希表中记录一次
                elif len(hash_map[num]) == 1:
                    # 在哈希表中再次记录该元素
                    hash_map[num].append(num_idx)
                    # 新数组同时记录该元素
                    new_nums.append(num)
                    num_idx += 1
                # 其它情况不做任何处理
                else:
                    pass
            
                
        result_list = [] # 存放元素三元组
        n = len(new_nums) 
        is_used_results = set() # 创建哈希表,协助判断元素三元组是否重复
        for i in range(n):  
            for j in range(i+1, n):  
                if -(new_nums[i] + new_nums[j]) in hash_map: 
                     for k in hash_map[-(new_nums[i] + new_nums[j])]: # 查找目标值, 依次返回目标值索引k
                        if k == i:
                            continue # 第一次去重,避免元素三元组出现重复的元素
                        elif k == j:
                            continue # 第一次去重,避免元素三元组出现重复的元素
                        else:
                            sorted_result = tuple(sorted([new_nums[k], new_nums[i], new_nums[j]]))
                            if sorted_result in is_used_results: # 涉及查找时,用哈希表最快
                                pass # 第二次去重,避免结果列表中出现重复的元素三元组
                            else:
                                result_list.append([new_nums[k], new_nums[i], new_nums[j]])  
                                is_used_results.add(sorted_result)  
  
        return result_list

运行结果
在这里插入图片描述
复杂度分析

  • 时间复杂度:O(N2),其中 N 是新数组new_nums元素的数量。
    • 双层循环遍历新数组 ===> O(N2)
  • 空间复杂度:O(N)
    • 需要用哈希表列表存放新数组 ===> O(N)

结束语

  • 亲爱的读者,感谢您花时间阅读我们的博客。我们非常重视您的反馈和意见,因此在这里鼓励您对我们的博客进行评论。
  • 您的建议和看法对我们来说非常重要,这有助于我们更好地了解您的需求,并提供更高质量的内容和服务。
  • 无论您是喜欢我们的博客还是对其有任何疑问或建议,我们都非常期待您的留言。让我们一起互动,共同进步!谢谢您的支持和参与!
  • 我会坚持不懈地创作,并持续优化博文质量,为您提供更好的阅读体验。
  • 谢谢您的阅读!

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

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

相关文章

nodejs微信小程序+python+PHP血液中心管理平台的设计与实现-计算机毕业设计推荐

在二十一世纪的今天&#xff0c;我国献血总量已经不容小觑&#xff0c;在全国人民的不懈努力下&#xff0c;贫血、缺血的病人已经有了足够的血液保障。与此同时&#xff0c;采血工作和血液入库、出库等工作也日愈繁重。为进一步提高采血工作和血液中心的工作效率&#xff0c;开…

【算法与数据结构】376、LeetCode摆动序列

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题难点在于要考虑到不同序列的情况&#xff0c;具体来说要考虑一下几种特殊情况&#xff1a; 1、上…

提前预警,时刻守护:迅软DLP的数据安全先锋

许多数据泄密事件的发生&#xff0c;往往都是由于没有在案发事前做好安全保护&#xff0c;使得重要信息被随意攻击、盗取、泄密。比起在危机发生后亡羊补牢&#xff0c;更重要的是应该在案发之前未雨绸缪。迅软DLP作为迅软股份研发的“重磅选手”&#xff0c;可为政企单位在一切…

物联网智能仓库解决方案

物联网智能仓库解决方案是一种基于物联网技术的仓库管理系统&#xff0c;通过自动化设备、智能化管理系统和大数据分析等技术&#xff0c;实现仓库的智能化运营和管理。 物联网智能仓库解决方案包括&#xff1a; 仓库设备自动化&#xff1a;通过自动化设备和技术&#xff0c;实…

OpenHarmony关于修改系统横屏导致启动视频显示不全问题解决

前言 OpenHarmony源码版本&#xff1a;4.0release 开发板&#xff1a;DAYU / rk3568 前段时间写的设置OpenHarmony启动视频&#xff0c;在竖屏状态下是正常的&#xff0c;但是横屏状态下显示不全。 链接直达&#xff1a;OpenHarmony 设备启动Logo和启动视频替换指南-CSDN博…

docker小白第四天

docker小白第一天 什么是镜像 1、是一种轻量级、可执行的独立软件包&#xff0c;它包含运行某个软件所需的所有内容&#xff0c;我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等)&#xff0c;这个打包好的运行环境就…

基于轻量级yolov5-seg全系列【n/s/m/l/x】参数模型开发构建工业场景下不同参数量级的滚珠丝杠传动表面缺陷分割检测系统

工业场景下的滚珠丝杠传动表面缺陷分割检测系统在我们前面的博文中已经有了相关的开发实践了&#xff0c;感兴趣的话可以自行阅读即可&#xff1a; 《助力工业生产质检&#xff0c;基于轻量级yolov5-seg开发构建工业场景下滚珠丝杠传动表面缺陷分割检测系统》 前文主要是以se…

【PS】修改 图片 文字

删除文字 1&#xff1a;框选要修改的文字 选择-色彩范围 调整色彩容差能看见字体的时候就OK&#xff08;记住用吸管吸取文字颜色&#xff09; 2&#xff1a;选择-修改-扩展-像素2 3&#xff1a;编辑-内容识别填充 现在文字去除了。 用污点画笔修复工具&#xff0c;对缺陷进行…

四十七、Redis分片集群

目录 一、分片集群结构 二、散列插槽 1、Redis如何判断某个key应该在哪个实例&#xff1f; 2、如何将同一类数据固定的保存在同一个Redis实例&#xff1f; 三、集群伸缩 四、故障转移 1、当集群中有一个master宕机时 &#xff08;1&#xff09;自动转移 &#xff08;2&…

[渗透测试学习] Codify - HackTheBox

首先nmap扫描端口 nmap -sV -sC -p- -v --min-rate 1000 10.10.11.239扫出来三个端口&#xff0c;22端口为ssh服务&#xff0c;80端口有http服务&#xff0c;3000端口为nodejs框架 尝试访问下80端口&#xff0c;发现页面重定向 将该域名添加到hosts里 sudo vim /etc/hosts 成…

MySQL数据库的基础概念

目录 顾名思义&#xff0c;数据库是用于存储数据的&#xff0c;那这些数据被存储在哪呢&#xff1f; 文件也能存储数据&#xff0c;那在这个基础上&#xff0c;为什么还要搞出一个数据库来存储数据呢&#xff1f; MySQL的客户端登录/退出指令、服务端的启动/关闭指令 数据…

mysql数据库损坏后重装,数据库备份

重装 先卸载 sudo apt-get remove --purge mysql-server mysql-client mysql-common sudo apt-get autoremove sudo apt-get autoclean 然后重新安装MySQL: sudo apt-get install mysql-server mysql-client 首先要先使用无密码登录数据库一定要使用 sudo mysql -uroo…

数据结构(七):树介绍及面试常考算法

一、树介绍 1、定义 树形结构是一种层级式的数据结构&#xff0c;由顶点&#xff08;节点&#xff09;和连接它们的边组成。 树类似于图&#xff0c;但区分树和图的重要特征是树中不存在环路。树有以下特点&#xff1a; &#xff08;1&#xff09;每个节点有零个或多个子节点…

企业微信旧版-新版网络连接错误,无法登录的解决方案

一.企业微微信无法登录故障 二.解决方案 1.网上的解决方案 **检查网络连接&#xff1a;**确保你的计算机正常连接到互联网。尝试打开其他网页&#xff0c;以确保网络连接正常。 **防火墙和安全软件&#xff1a;**某些防火墙或安全软件可能会阻止企业微信的正常连接。请确保你…

computed 和 watch 的奇妙世界:让数据驱动你的 Vue 应用(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

idea2023解决右键没有Servlet的问题

复制Servlet Class.java中的文件。 回到文件&#xff0c;然后点击小加号 然后输入刚刚复制的东西&#xff1a; 3. 此时右键有servlet。 4. 然后他让你输入下面两个框&#xff1a; JAVAEE TYPE中输入Servlet Class Name 表示你要创建的Servlet类的名称是什么。自己起名字。然后…

寒冷冬天,再次撕下了新能源汽车的续航遮羞布,北方真不适合

随着懂车帝的冬测&#xff0c;新能源汽车的冬天续航成为关注焦点&#xff0c;电池性能在冬天里缩减众所周知&#xff0c;不过从来没有机构告诉消费者&#xff0c;到底冬天电池的续航会减少多少&#xff0c;如今这一切显然暴露在人们眼前了。 懂车帝的冬测显示除了特别优秀的新能…

Elasticsearch的 8.x常用api汇总

ES的查询语法比较复杂,对于初学者需要在不断练习中才会逐渐掌握,本文汇总了ES各种查询语法以及常用api,可以作为新手的实用笔记 首先,安装 Kibana! 下载Elasticsearch,官方下载页面;Elasticsearch 参考,官方文档;<

「构」向云端 - 我与 2023 亚马逊云科技 re:Invent 大会

授权声明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道 2023年亚马逊AWS re:Invent大会宣布一项Amazon Q的创新项目&#x…

为什么 GAN 不好训练

为什么 GAN 不好训练&#xff1f;先看 GAN 的损失&#xff1a; 当生成器固定时&#xff0c;堆D(x)求导&#xff0c;推理得到&#xff08;加号右边先对log求导&#xff0c;再对负项求导&#xff09; 然后在面对最优Discriminator时&#xff0c;Generator的优化目标就变成了&…