代码随想录阅读笔记-哈希表【三数之和】

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

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

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

思路 

这道题与前面提到的两数之和以及四数之和Ⅱ在题意上非常类似,大家第一想法可能就是使用哈希法来处理,但是事实证明哈希解法此时并不是明智的选择。

哈希解法

两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。

把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。

去重的过程不好处理,有很多小细节,如果在面试中很难想到位。

时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。

哈希法C++代码:

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(), nums.end());// 找出a + b + c = 0// a = nums[i], b = nums[j], c = -(a + b)for (int i = 0; i < nums.size(); i++) {// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组if (nums[i] > 0) {break;}if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重continue;}unordered_set<int> set;for (int j = i + 1; j < nums.size(); j++) {if (j > i + 2&& nums[j] == nums[j-1]&& nums[j-1] == nums[j-2]) { // 三元组元素b去重continue;}int c = 0 - (nums[i] + nums[j]);if (set.find(c) != set.end()) {result.push_back({nums[i], nums[j], c});set.erase(c);// 三元组元素c去重} else {set.insert(nums[j]);}}}return result;}
};
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n),额外的 set 开销

可以看到剪枝逻辑非常复杂,尤其是去重的逻辑判断上,所以这里引出更加好理解并且效率高的双指针法。

双指针法

其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。

这道题目使用双指针法 要比哈希法高效一些,那么来讲解一下具体实现的思路。

动画效果如下:

15.三数之和

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

时间复杂度:O(n^2)。

C++代码代码如下:

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {vector<vector<int>> result;sort(nums.begin(), nums.end());// 找出a + b + c = 0// a = nums[i], b = nums[left], c = nums[right]for (int i = 0; i < nums.size(); i++) {// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了if (nums[i] > 0) {return result;}// 错误去重a方法,将会漏掉-1,-1,2 这种情况/*if (nums[i] == nums[i + 1]) {continue;}*/// 正确去重a方法if (i > 0 && nums[i] == nums[i - 1]) {continue;}int left = i + 1;int right = nums.size() - 1;while (right > left) {// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组/*while (right > left && nums[right] == nums[right - 1]) right--;while (right > left && nums[left] == nums[left + 1]) left++;*/if (nums[i] + nums[left] + nums[right] > 0) right--;else if (nums[i] + nums[left] + nums[right] < 0) left++;else {result.push_back(vector<int>{nums[i], nums[left], nums[right]});// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重while (right > left && nums[right] == nums[right - 1]) right--;while (right > left && nums[left] == nums[left + 1]) left++;// 找到答案时,双指针同时收缩right--;left++;}}}return result;}
};
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(1)

 注意点

a的去重:

说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]

a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。

但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。

有人可能想,这不都一样吗。

其实不一样!

都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。

如果我们的写法是 这样:

if (nums[i] == nums[i + 1]) { // 去重操作continue;
}

那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!

所以这里是有两个重复的维度。

那么应该这么写:

if (i > 0 && nums[i] == nums[i - 1]) {continue;
}

这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。

这是一个非常细节的思考过程。

b与c的去重:

很多人写本题的时候,去重的逻辑多加了 对right 和left 的去重:(代码中注释部分)

while (right > left) {if (nums[i] + nums[left] + nums[right] > 0) {right--;// 去重 rightwhile (left < right && nums[right] == nums[right + 1]) right--;} else if (nums[i] + nums[left] + nums[right] < 0) {left++;// 去重 leftwhile (left < right && nums[left] == nums[left - 1]) left++;} else {}
}

但细想一下,这种去重其实对提升程序运行效率是没有帮助的。

拿right去重为例,即使不加这个去重逻辑,依然根据 while (right > left) 和 if (nums[i] + nums[left] + nums[right] > 0) 去完成right-- 的操作。

多加了 while (left < right && nums[right] == nums[right + 1]) right--; 这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。

最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。

所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android和IOS应用开发-Flutter 应用中实现记录和使用全局状态的几种方法

文章目录 在Flutter中记录和使用全局状态使用 Provider步骤1步骤2步骤3 使用 BLoC步骤1步骤2步骤3 使用 GetX&#xff1a;步骤1步骤2步骤3 在Flutter中记录和使用全局状态 在 Flutter 应用中&#xff0c;您可以使用以下几种方法来实现记录和使用全局状态&#xff0c;并在整个应…

git:码云gitee仓库提交以及React项目创建

git&#xff1a;码云gitee仓库提交以及React项目创建 1 前言 先注册准备好码云gitee的账户&#xff0c;并在gitee上新建react仓库并提交代码至远程仓库。 2 操作方式 准备新建React项目并提交到码云gitee上。 &#xff08;1&#xff09;进入官网&#xff1a;https://gitee…

day-23 买卖股票的最佳时机 II

思路&#xff1a;因为要求的是最大收益&#xff0c;所以可以假设每天都买&#xff0c;第二天卖 利用一个数组来统计&#xff0c;如果收益为负则ans[i]0,否则ans[i]prices[i1]-prices[i] code: class Solution {public int maxProfit(int[] prices) {int nprices.length;int a…

数据在内存的存储

整数在内存中的存储 我们来回顾一下&#xff0c;整数在计算机是以补码的形式进行存储的&#xff0c;整数分为正整数和负整数&#xff0c;正整数的原码、反码和补码是一样的&#xff0c;负整数的原码、反码和补码略有不同&#xff08;反码是原码除符号位&#xff0c;其他位按位取…

【九】【算法分析与设计】双指针(3)

15. 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元…

echarts实践总结(常用一):柱状图(特点:渐变色、点击缩放、左右滑动、悬浮展示样式)

目录 第一章 echarts基本使用 第二章 echarts实践——柱状图 效果展示 第一章 echarts基本使用 Echarts常用配置项(详细入门)_echarts配置项手册-CSDN博客 第二章 echarts实践——柱状图 最近接到这么一个需求&#xff0c;需要画页面&#xff0c;然后有这么几个echarts的图需…

【网络安全】0xhacked CTF 大赛题解出炉啦!

此次 0xhacked CTF 比赛&#xff0c;ChainSecLabs 取得了第四名的成绩。让我们来看看比赛题目的题解吧。&#xff08;题目代码仓库在文末哦~&#xff09; BabyOtter 这是应该说是一个算法题&#xff0c;很明显需要溢出&#xff0c;因为精度问题&#xff0c;uint256(-1)/0x1…

m4v是什么文件格式?m4v视频用什么软件打开?

m4v文件格式的诞生可追溯到苹果公司。作为数字媒体领域的先锋&#xff0c;苹果在iTunes商店中为视频内容引入了m4v格式。其初衷是为了在保证视频质量的同时&#xff0c;通过管理系统&#xff0c;实现对数字内容的保护。这使得m4v成为iOS和macOS平台上广泛使用的视频格式。 M4V的…

工具精灵--超级好用的在线工具网站

工具精灵是一个超级好用的在线工具网站&#xff0c;它有这些功能&#xff1a;json格式化、xml格式化、markdown在线编辑、sql格式化、json转Java、xml转Java等。 虽然有很多这种类似的网站了&#xff0c;但它们并不好用&#xff0c;很粗糙。工具精灵超级好用&#xff0c;细节方…

为什么要为 App 应用加固 ?如何为 App 应用加固 ?

一&#xff1a;为什么要为 App 应用加固 来看下 腾讯开放平台 官方的解释说明 若应用不做任何安全防护&#xff0c;极易被病毒植入、广告替换、支付渠道篡改、钓鱼、信息劫持等&#xff0c;严重侵害开发者的利益。 App 加固后&#xff0c;可以对应用进行安全防护&#xff0c;防…

回归测试?

1. 什么是回归测试&#xff08;Regression Testing&#xff09; 回归测试是一个系统的质量控制过程&#xff0c;用于验证最近对软件的更改或更新是否无意中引入了新错误或对以前的功能方面产生了负面影响&#xff08;比如你在家中安装了新的空调系统&#xff0c;发现虽然新的空…

抛弃Superhuman?这些替代方案让你眼前一亮!

Superhuman是一个极好的人工智能工具在电子邮件助理领域。根据SimilarWeb的最新统计&#xff0c;它在全球网站排名中排名第21980位&#xff0c;月访问量为1751798。然而市场上还有许多其他优秀的选择。为了帮助您找到最适合您需求的解决方案&#xff0c;我们为您精心挑选了10种…

项目性能优化—使用JMeter压测SpringBoot项目

我们的压力测试架构图如下&#xff1a; 配置JMeter 在JMeter的bin目录&#xff0c;双击jmeter.bat 新建一个测试计划&#xff0c;并右键添加线程组&#xff1a; 进行配置 一共会发生4万次请求。 ctrl s保存&#xff1b; 添加http请求&#xff1a; 配置http请求&#xff1a;…

免费开源、支持自建服务的团队协作、个人学习文档管理系统

大家好&#xff0c;我是小麦。今天来给大家分享的是几款个人使用过的免费、开源、适合团队协作的文档管理工具&#xff0c;并且是完全支持自己搭建服务的文档管理系统。 相信大家在学习、办公等场景下对文档管理工具使用的场景是比较多的&#xff0c;例如技术开发手册、个人学…

SpringBoot(数据库操作 + druid监控功能)

文章目录 1.JDBC HikariDataSource&#xff08;SpringBoot2默认数据源&#xff09;1.数据库表设计2.引入依赖 pom.xml3.配置数据源参数 application.yml4.编写一个bean&#xff0c;映射表5.编写测试类来完成测试1.引入依赖 pom.xml2.使用JdbcTemplate进行测试3.成功&#xff0…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的交通信号灯识别系统(深度学习+UI界面+训练数据集+Python代码)

摘要&#xff1a;本研究详细介绍了一种采用深度学习技术的交通信号灯识别系统&#xff0c;该系统集成了最新的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等早期算法进行了性能评估对比。该系统能够在各种媒介——包括图像、视频文件、实时视频流及批量文件中——准确地…

Python从COCO数据集中抽取某类别的数据

1、问题描述 今天需要训练一个人工智能检测模型&#xff0c;用于检测图片或视频中的人。自行收集训练数据费时费力&#xff0c;因而选择从公开数据集COCO中进行抽取。 2、数据准备 2.1 下载 COCO2017 数据集 train:http://images.cocodataset.org/zips/train2017.zip valid…

2023年蓝桥杯省赛——幸运数字

目录 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 思路 高级思路 总结 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 首先是我写了差不多一个小时的解法&#xff0c;裂开了&#xff0c;为什么我如此废物 思路 寻找第2023个在二进制、八…

http协议的历史与基本概念

文章目录 历史和发展起源&#xff1a;HTTP/0.9&#xff08;1991年&#xff09;&#xff1a;HTTP/1.0&#xff08;1996年&#xff0c;RFC 1945&#xff09;&#xff1a;HTTP/1.1&#xff08;1997年&#xff0c;RFC 2068&#xff1b;1999年更新为RFC 2616&#xff09;&#xff1a…

【MIT 6.S081】2020, 实验记录(9),Lab: file system

目录 Task 1&#xff1a;Large filesTask 2&#xff1a;Symbolic links2.1 增加一个系统调用 symlink2.2 新增文件类型2.3 新增 NOFOLLOW 标志位2.4 实现 sys_symlink 系统调用2.5 修改 sys_open 函数2.6 测试 Task 1&#xff1a;Large files 现在的 xv6 系统中&#xff0c;一…