15. 三数之和(力扣LeetCode)

文章目录

  • 15. 三数之和
    • 题目描述
    • 双指针
      • 去重逻辑的思考
        • a的去重
        • b与c的去重

15. 三数之和

题目描述

给你一个整数数组 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

双指针

其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。

而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。

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

动画效果如下:
在这里插入图片描述
拿这个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)。

class Solution {
public:// 主函数,调用此函数来找到所有不重复的三数之和为零的组合vector<vector<int>> threeSum(vector<int>& nums) {// 注释掉的快速排序,留作参考或者选择排序方法// quick_sort(nums, 0, nums.size() - 1); // 快速排序// 使用归并排序对数组进行排序merge_sort(nums, 0, nums.size() - 1);// 定义用于存放结果的二维向量vector<vector<int>> result;// 找出a + b + c = 0// a = nums[i], b = nums[left], c = nums[right]// 遍历排序后的数组,寻找三数之和为零的组合for (int i = 0; i < nums.size(); i++) {// 如果当前数字大于0,则后续不可能找到三数之和为零的组合(因为数组已排序)if (nums[i] > 0) break;// 错误去重a方法,将会漏掉-1,-1,2 这种情况/*if (nums[i] == nums[i + 1]) {continue;}*/// 去重:跳过连续相同的数字,以避免重复的三元组if (i > 0 && nums[i] == nums[i - 1]) continue;// 定义左指针和右指针int l = i + 1, r = nums.size() - 1;// 当左指针小于右指针时,执行循环while (l < r) {// 去重复逻辑如果放在这里,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++;*/// 计算三数之和int sum = nums[i] + nums[l] + nums[r];// 根据三数之和与零的比较,移动指针if (sum > 0) r--; // 和大于零,移动右指针以减小和else if (sum < 0) l++; // 和小于零,移动左指针以增大和else {// 找到有效的三元组,加入结果集result.push_back({nums[i], nums[l], nums[r]});// 去重:跳过左侧连续相同的数字while (l < r && nums[l] == nums[l + 1]) l++;// 去重:跳过右侧连续相同的数字while (l < r && nums[r] == nums[r - 1]) r--;// 移动左右指针准备寻找下一个可能的组合l++, r--;}}}// 返回最终的结果集return result;}private:// 快速排序函数,已注释掉,但可供选择使用void quick_sort(vector<int>& n, int l, int r) {if (l >= r) return;// 快速排序的分区操作int i = l - 1, j = r + 1, x = n[l + r >> 1];while (i < j) {do i++; while (n[i] < x);do j--; while (n[j] > x);if (i < j) swap(n[i], n[j]);}// 递归排序左半部quick_sort(n, l, j);// 递归排序右半部quick_sort(n, j + 1, r);}// 用于归并排序的临时数组int tmp[3000];// 归并排序函数void merge_sort(vector<int>& n, int l, int r) {if (l >= r) return; // 如果区间只有一个元素或为空,则不进行操作// 计算中点,用于分割数组int mid = l + r >> 1;// 递归排序左半部分merge_sort(n, l, mid);// 递归排序右半部分merge_sort(n, mid + 1, r);// 归并操作:合并两个有序数组int i = l, j = mid + 1, k = 0;while (i <= mid && j <= r) {// 选取两个数组中较小的一个加入到临时数组中if (n[i] < n[j]) tmp[k++] = n[i++];else tmp[k++] = n[j++];}// 将剩余元素加入临时数组while (i <= mid) tmp[k++] = n[i++];while (j <= r) tmp[k++] = n[j++];// 将临时数组中的元素复制回原数组for (int i = l, j = 0; i <= r; i++, j++) n[i] = tmp[j];}
};

去重逻辑的思考

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/659998.shtml

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

相关文章

全志H713红外IR遥控配置方法

篇头 全志H713 Soc是一颗 A53四核心&#xff0c;支持MAX 2GB DDR&#xff0c; 支持1920x1080P LVDS接口&#xff0c; 支持梯形校正功能的芯片&#xff0c;非常适合用于开发投影仪&#xff0c;尤其是低成本的LCD投影。本文详细介绍此平台&#xff0c;配置一个新的红外遥控器的方…

【无刷电机学习】电流采样电路硬件方案

【仅作自学记录&#xff0c;不出于任何商业目的】 目录 AD8210 INA282 INA240 INA199 AD8210 【AD8210数据手册】 在典型应用中&#xff0c;AD8210放大由负载电流通过分流电阻产生的小差分输入电压。AD8210抑制高共模电压(高达65V)&#xff0c;并提供接地参考缓冲输出&…

gtkmm xml ui 例子(from string)

文章目录 前言来看一个从字符串中生成UI的例子 前言 glade生成的xml格式不被gtkmm4支持, 需要作修改 来看一个从字符串中生成UI的例子 #include <gtkmm/application.h> #include <gtkmm.h> #include <iostream> using namespace std;class ExampleWindow :…

51单片机编程应用(C语言):独立按键

目录 1.独立按键介绍 2.独立按键控制LED亮灭 1.1按下时LED亮&#xff0c;松手LED灭&#xff08;按一次执行亮灭&#xff09; 1.2首先按下时无操作&#xff0c;松手时LED亮&#xff08;再按下无操作&#xff0c;所以LED亮&#xff09;&#xff0c;松手LED灭&#xff08;松手时…

音频几个相关概念及心理声学模型

系列文章目录 音频格式的介绍文章系列&#xff1a; 音频编解码格式介绍&#xff1a;音频几个相关概念及心理声学模型 https://blog.csdn.net/littlezls/article/details/135499627 音频编解码格式介绍&#xff1a;音频编码格式介绍 https://blog.csdn.net/littlezls/article/d…

RabbitMQ快速上手

首先他的需求实在什么地方。我美哟明显的感受到。 它给我的最大感受就是脱裤子放屁——多此一举&#xff0c;的感觉。 他将信息发送给服务端中间件。在由MQ服务器发送消息。 服务器会监听消息。 但是它不仅仅局限于削峰填谷和稳定发送信息的功能&#xff0c;它还有其他重要…

标准化编程系列(常用模式状态介绍)

任何事情任何编程都是有方法可循的,我们所要做的工作就是在看似没有规律的运行中,发现规律总结一般性的方法。这篇博客主要介绍标准化编程相关的基础知识,编程化编程离不开大家扎实的编程基本功,所以在学习标准化的同时,大家需要提升对于子程序,模块FB 、FC等的应用知识,…

【Linux网络编程一】网络基础(网络框架)

【Linux网络编程一】网络基础&#xff08;网络框架&#xff09; 一.什么是协议1.通信问题2.协议本质3.网络协议标准 二.协议分层1.为什么协议要分层2.如何具体的分层 三.操作系统OS与网络协议栈的关系1.核心点&#xff1a;网络通信贯穿协议栈 四.局域网中通信的基本原理1.封装&…

HiveSQL题——数据炸裂和数据合并

目录 一、数据炸裂 0 问题描述 1 数据准备 2 数据分析 3 小结 二、数据合并 0 问题描述 1 数据准备 2 数据分析 3 小结 一、数据炸裂 0 问题描述 如何将字符串1-5,16,11-13,9" 扩展成 "1,2,3,4,5,16,11,12,13,9" 且顺序不变。 1 数据准备 with da…

【Windows】用来替代令人发指的Windows自带图片浏览器的一些免费软件

缘由&#xff1a;今天[2024.2.1] 使用"照片"打开图片时出现如下提示 如果是涉及到一些本地无法实现的功能&#xff08; 比如AI方面的图像处理 &#xff09;要去联网我无可厚非&#xff0c; 但是我只是TMD查看图片而已&#xff0c;就想偷偷上传我的图片就过分了&…

【Tomcat与网络6】 Tomcat是如何扩展Java线程池的?

目录 1.Java 的线程池 2.Tomcat 的线程池 学习Tomcat的时候&#xff0c;有很多绚丽的技术值得我们学习&#xff0c;但是个人认为Tomcat的线程池扩展是最值得研究的一个部分&#xff0c;线程池的应用太广了&#xff0c;也重要了&#xff0c;Java原生线程池的特征我相信很多人都…

服务攻防-开发组件安全Solr搜索Shiro身份Log4j日志本地CVE环境复现

知识点&#xff1a; 1、J2EE-组件安全-Solr-全文搜索 2、J2EE-组件安全-Shiro-身份验证 3、J2EE-组件安全-Log4J-日志记录 章节点&#xff1a; 1、目标判断-端口扫描&组合判断&信息来源 2、安全问题-配置不当&CVE漏洞&弱口令爆破 3、复现对象-数据库&中间…

XPath从入门到精通:基础和高级用法完整指南,附美团APP匹配示例

XPath 通常用来进行网站、XML (APP )和数据挖掘&#xff0c;通过元素和属性的方式来获取指定的节点&#xff0c;然后抓取需要的信息。 学习 XPath 语法之前&#xff0c;首先了解一些概念。 概念介绍 节点之间的关系 以上面的 HTML 节点树为例&#xff0c;节点之间包含了下列…

Linux——安装MySQL

1、安装mysql8.0.35 1.1、安装步骤 1.更新包列表&#xff0c;首先&#xff0c;确保您的系统已更新到最新状态。运行以下命令来更新包列表和安装最新的软件包&#xff1a; sudo apt update sudo apt upgrade2.安装MySQL服务器&#xff1a;运行以下命令来安装MySQL服务器&…

win11安装wsl作为linux子系统并当作服务器

wsl安装 打开控制面板&#xff0c;找到启用或关闭windows功能 开启windows虚拟机监控平台和适用于Linux的Windows子系统&#xff0c;重启电脑。 打开microsoft store搜索ubuntu&#xff0c;找到合适的版本下载安装 输入wsl -l如下所示&#xff0c;即为安装成功。 安装过程比较…

Golang语言异常机制解析:错误策略与优雅处理

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 作为开发者来说&#xff0c;我们没办法保证程序在运行过程中永远不会出现异常&#xff0c;对于异常…

Maven:Dmaven.multiModuleProjectDirectory system propery is not set.

eclipse中使用maven插件的时候&#xff0c;运行run as maven build的时候报错 -Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match. 直接的解决方法&#xff1a;使用低版本的maven 可以设一个环境变量…

分布式搜索引擎_学习笔记_2

分布式搜索引擎_学习笔记_2 在昨天的学习中&#xff0c;我们已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。 所以今天&#xff0c;我们研究下elasticsearch的数据搜索功能。我们会分别使用…

Vue.js 中子组件向父组件传值的方法

Vue.js 是一款流行的 JavaScript 前端框架&#xff0c;它提供了一套完整的工具和 API&#xff0c;使得开发者可以更加高效地构建交互式的 Web 应用程序。其中&#xff0c;组件化是 Vue.js 的一个核心概念&#xff0c;通过组件化可以将一个复杂的应用程序拆分成多个独立的部分&a…

系统架构设计师考试大纲2023

一、 考试方式&#xff08;机考&#xff09; 考试采取科目连考、 分批次考试的方式&#xff0c; 连考的第一个科目作答结束交卷完成后自动进 入第二个科目&#xff0c; 第一个科目节余的时长可为第二个科目使用。 高级资格&#xff1a; 综合知识科目考试时长 150 分钟&#xff…