双指针算法第二弹(查找总价格为目标值的两个商品-和为s的两个数字 三数之和 四数之和)

系列文章目录

《双指针算法第一弹(移动零 复写零 快乐数)》链接:http://t.csdnimg.cn/Nqdvn


目录

系列文章目录

前言

1. 查找总价格为目标值的两个商品

(1)题目及示例

(2)思路(由浅入深)

2. 三数之和

(1)题目及示例

(2)一般思路

(2)双指针优化

3. 四数之和

(1)题目及示例

(2)一般思路

(3)双指针优化

总结


前言

本篇文章开启双指针算法第二弹,一起来感受双指针算法的魅力。这三道OJ题思路相似,难度由易到难,可以按照下面顺序自己挑战一下。每道题目中都带有链接,不用再去Leetcode寻找了!


1. 查找总价格为目标值的两个商品

(1)题目及示例

题目:购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况,返回任一结果即可。

链接:. - 力扣(LeetCode)

示例 1:

输入:price = [3, 9, 12, 15], target = 18

输出:[3,15] 或者 [15,3]

示例 2:

输入:price = [8, 21, 27, 34, 52, 66], target = 61

输出:[27,34] 或者 [34,27]

(2)思路(由浅入深)

分析:这道题目让我们在给出的数组中寻找两个数字的总和刚好是target。其中这个数组是个升序数组,已经排好序了。

如果我们用正常思维,最先想到应该是暴力解法。写两层for循环,第一层循环固定第一个数,第二层循环从固定数后一个数开始寻找符合题目要求的数,然后固定的数从首元素开始到倒数第二个元素结束。两层for循环,最坏的情况是遍历n-1,n-2,……,1,加起来就是N^{2}级别,时间复杂度O(N^{2}),空间复杂度是O(1)。如果你把下面的代码写到Leetcode中去提交,大概率过不了,会超出时间限制。

    vector<int> twoSum(vector<int>& price, int target) {int n = price.size();for(int i = 0; i < n - 1; i++)for (int j = i + 1; j < n; j++)if (price[i] + price[j] == target)return {price[i], price[j]};return {-1, -1};//照顾编译器}

我们该如何优化呢?需要注意到题目给出的数组是升序的,我们可以利用这个性质。我们使用两个变量表示数组元素下标,用来指向数组元素。此时两元素之和为sum。

  • 如果其中一个变量加1,即指向后一个元素,因为数组是单调递增的,sum的值都会增大。
  • 如果其中一个变量减1,指向前一个元素,那么指向元素比之前的元素小,sum的值会减小。

如下图,target等于31,left和right表示数组元素的下标。我们不让left和right一开始都指向首元素,让left指向首元素,right指向末尾元素。sum此时为首尾元素之和,跟target无非就大于,小于和等于三种关系。

  • 下图中,sum小于target。如果使用暴力解法,right需要指向第二个元素9开始,然后往后挪动,寻找匹配的元素。可末尾元素是最大的,它和首元素相加都小于target,那么中间元素加上首元素肯定也小于target。因此,此时只需要将left++,指向后一个元素,sum才会增大,继续与target比较。

  • 同理,此时target为37,sum大于target。如果使用暴力解法,那么left固定在末尾元素之前,都会跟末尾元素相加。首元素与末尾元素相加大于target,况且中间元素全部都比首元素大,那么中间元素加上末尾元素肯定也大于target。此时,只需要right--,指向前一个元素,使得sum减小,才有可能与target相等。

  • 根据上面的分析再来实现代码,是很简单的。先定义三个变量left,right,sum,分别表示数组元素的下标和下标元素之和。使用while循环,循环条件是left小于right。
  • 循环内部就是sum赋值为两元素之和,然后跟target比较。当sum大于target时,需要减小肃穆,right--,同理sum小于target时,就是left++。
  • 如果相等,直接使用花括号返回两个元素,这样子的形式是隐士类型转换。
  • 最后再循环外面再随便返回两个值,题目中没有说明找不到的情况返回什么,但是不返回的话,编译器会报错,认为如果循环走完没有返回值,就会出问题,所以可以随便返回两个值。
    vector<int> twoSum(vector<int>& price, int target) {int left = 0, right = price.size() - 1, sum = 0;while(left < right){sum = price[left] + price[right];//三种情况if (sum > target)right--;else if (sum < target)left++;elsereturn {price[left], price[right]};//return vector{price[left], price[right]};}return {-1, -1};//照顾编译器}

2. 三数之和

(1)题目及示例

题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。

链接:. - 力扣(LeetCode)

示例 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 。

(2)一般思路

这道题我们使用暴力枚举,写三层for循环,找出所有的三元组合。再求和,寻找和为0的三元组。但是题目还有要求,输出的三元组不能重复,所以还需要检查和为0的三元组,进行去重操作

  • 如下图,示例1中的数组,有三个和为0的数组,肉眼观察就知道前两个是重复,那程序怎么辨别呢?可以对每个符合要求的三元组进行排序,再进行比较就可以去重。
  • 但是符合要求的三元组非常多,每一个进行排序,消耗很大,效率低。因此,可以一开始就排排升序,让和为0的三元组按顺序存放,然后再使用set自动去重,也可以手动去重。

下面的代码就是按照上面的思路实现的。

  • 数组先进行了排序,排序时间复杂度O(n\log n),但是使用了三层for循环,暴力枚举出所有三元组,时间复杂度是O(n^{3})。总的时间复杂度是 O(n\log n + n^{3})。在这个时间复杂度中,n^3 项是主导项,因此可以简化为 O(n^{3})。
  • vector 存储最终的三元组,最坏情况下需要 O(n^{3}) 的空间,即所有可能的组合都不重复。set 用于去重,最坏情况下也需要 O(n^{3}) 的空间,以存储所有不重复的三元组。因此,总的空间复杂度是 O(n^{3})。
vector<vector<int>> threeSum(vector<int>& nums)
{vector<vector<int>> ret;set<vector<int>> unique; // 使用set去重sort(nums.begin(), nums.end()); // 先对数组进行排序for (int i = 0; i < nums.size(); ++i) for (int j = i + 1; j < nums.size(); ++j) for (int k = j + 1; k < nums.size(); ++k) if (nums[i] + nums[j] + nums[k] == 0) {vector<int> tmp = {nums[i], nums[j], nums[k]};unique.insert(tmp); // 将三元组插入set中,自动去重}// 将set中的三元组拷贝到结果数组中for (const auto& e : unique) ret.push_back(e);return result;
}

(2)双指针优化

这道题其实跟上一道题解法类似,可以说是它的拓展。我们先对数组排升序,然后利用升序的性质使用双指针。不过这次需要寻找三个数字,可以固定首元素,寻找和为首元素的相反数的两个数。但是上一个题目只有寻找一对数。

在这道题中,如果找到一对符合要求的数字,还要继续寻找,直到两个变量相等,即指向同一个元素。需要注意,还要执行三个去重操作。target为固定元素的相反值,sum为left和right下标元素之和。

  • 如下图,先固定首元素,使用双指针寻找和为target的两个元素。第四个元素之前,sum小于target,所以left需要不断加1,往后移动。直到指向第四个元素时,sum等于target,left指向前一个元素,right指向后一个元素,并且需要跳过相同的数

  • 下面数组中,left和right指向的元素之和刚好为target。left++,right--,指向中间的元素,不过有两个2,相同的元素,right需要跳过去。
  • 此时left指向的元素值是1,right指向元素的值也是1,符合要求,会记录下来。left和right指向的元素相邻,说明这一轮已经结束。first需要指向后面的元素,并且它也要跳过相同的元素,避免重复

 

当我们知道需要三个去重操作之后,再去实现代码就比较容易。

  • 其中排序消耗的时间复杂度是O(n\log n),两层for循环的时间复杂度是O(n^2{}),综合起来,时间复杂度是O(n^2{})。
  • 空间上,使用了几个变量,还使用ret数组,数组的大小取决于输入数组中三元组的数量,最多也是O(n^2{})。
  • 首先对数组进行排序。其次使用for循环,固定首元素,直到倒数第三个元素。i表示固定元素的下标,我们一开始就判断,固定的元素跟前一个元素是否重复,重复就跳过,并且要注意先判断i大于0,不然会越界访问到前面内存空间并报错。
  • for循环内部就是寻找和为target的二元组,跟第一道题目类似。只不过再找到一个二元组时,也需要跳过重复的数字,进行去重操作。这里也需要注意while循环中继续的条件,先写left<right,这个是保证两个变量相等时,不会发生对数组的元素发生二次遍历。
vector<vector<int>> threeSum(vector<int>& nums) 
{sort(nums.begin(), nums.end());//对数组进行排序int n = nums.size();vector<vector<int>> ret;for (int i = 0; i < n - 2; ++i) {if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复的iint target = -nums[i];int left = i + 1, right = n - 1;while (left < right) {int sum = nums[left] + nums[right];if (sum < target) ++left;else if (sum > target) --right;else {                  ret.push_back({nums[i], nums[left], nums[right]});++left;while (left < right && nums[left] == nums[left - 1]) ++left; // 跳过重复的left--right;while (left < right && nums[right] == nums[right + 1]) --right; // 跳过重复的right}}}return ret;
}

3. 四数之和

(1)题目及示例

题目:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

链接:. - 力扣(LeetCode)

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0

输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8

输出:[[2,2,2,2]]

(2)一般思路

这道题目是三数之和的升级,寻找符合要求的四个数。

暴力解法也类似,先进行排序,再嵌套四层for循环,枚举出所有四元组,再求和比较是否与target相等。使用set去重,或者手动去重。这里就不在写代码了。

暴力解法中,使用sort排序时间复杂度O(n\log n),再使用了四层for循环,枚举出所有四元组,时间复杂度是O(n^{4})。总的来说,时间复杂度就是O(n^{4})。

(3)双指针优化

四数之和,是三数之和的延伸。两道题思路大体是一样的,先写两层for循环来固定两个数,下标分别为i和j,然后再转换成寻找和为target-nums[i]-nums[j]的二元组,又变成了如何解决第一道题目。不过这道题需要去重的地方有四处。

  • first固定第一个数,跳过重复元素。
  • second固定第二个数,跳过重复元素。
  • left和right双指针,跳过重复元素。

实现代码时,需要注意Leetcode给出的测试用例中,target超出int整型范围,会造成溢出,需要换成long long来定义变量。

    vector<vector<int>> fourSum(vector<int>& nums, int target) {sort(nums.begin(), nums.end());//对数组进行排序int n = nums.size();vector<vector<int>> ret;//存储四元组for (int i = 0; i < n - 3; ++i) //固定第一个数{if (i > 0 && nums[i - 1] == nums[i])//跳过重复的数字continue;//利用三数之和for (int j = i + 1; j < n - 2; ++j) //固定第二个数{if (j > i + 1 && nums[j - 1] == nums[j])//跳过重复的数字,需要注意不能写成j>0continue;int left = j + 1, right = n - 1;//示例target太大,会整型溢出,用long long类型转换一下long long aim = (long long)target - nums[i] - nums[j];// 利用双指针解决while(left < right){int sum = nums[left] + nums[right];if (sum > aim)--right;else if (sum < aim)++left;else{ret.push_back({nums[i], nums[j], nums[left], nums[right]});++left;while(left < right && nums[left] == nums[left - 1])++left;// 跳过重复的left--right;while(left < right && nums[right] == nums[right + 1])--right;// 跳过重复的right}}}}return ret;}


总结

通过这三道题目的锻炼,想必对双指针算法有自己的理解,尽量捋顺思路后,自己动手实现代码,看看有哪些坑点。多说无益,自己动手吧!

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

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

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

相关文章

纯css写一个动态圣诞老人

效果预览 在这篇文章中&#xff0c;我们将学习如何使用CSS来创建一个生动的圣诞老人动画。通过CSS的魔力&#xff0c;我们可以让圣诞老人在网页上摇摆&#xff0c;仿佛在向我们招手庆祝圣诞节和新年。 实现思路 实现这个效果的关键在于CSS的keyframes动画规则以及各种CSS属性…

想要打造高效活跃的私域社群,这些技巧要知道

对一些企业来说“做社群等于做私域”。 在腾讯提到的私域转化场景中&#xff0c;社群与小程序、官方导购三者并列。 社群连接着品牌和群内用户。品牌通过圈住更多用户&#xff0c;来持续免费触达用户实现变现&#xff0c;用户则是从品牌方手中直接获取更多服务和优惠。那么&a…

【绝对有用】yolo系列目标检测 核心技术点 汇总

YOLO (You Only Look Once) 是一种高效的目标检测算法&#xff0c;它以速度和精度著称。YOLO 的工作原理是将目标检测视为一个回归问题&#xff0c;直接从图像的像素空间预测目标的类别和位置。YOLO 目标检测头包括以下几个关键部分&#xff1a; 输入图像处理&#xff1a; YOLO…

云计算【第一阶段(19)】磁盘管理与文件系统 LVM与磁盘配额(二)

目录 一、LVM概述 1.1、LVM机制的基本概念 ​编辑 1.2、LVM的管理命令 1.3、lvm存储 两种机制 1.4、lvm应用实例 二、磁盘配额概述 2.1、设置磁盘配额 2.2.1、实现磁盘限额的条件 2.2.2、linux磁盘限额的特点 2.2.3、磁盘配额管理 一、LVM概述 1.1、LVM机制的基本概…

用Python制作一个简单的计算器(加减乘除)

简易计算器 写在前面 小编用python实现了一个简单的计算器&#xff0c;一起来看看吧~ 需要环境&#xff1a; pycharm python 一、需求分析 1.1 功能分析 使用Python的Tkinter界面设计实现一个简单的计算器&#xff0c;主要功能按钮包括数字键、四则运算符、等于号和清除…

JavaScript算法之龟兔赛跑

简介:龟兔赛跑算法,又称弗洛伊德循环检测算法,是一种在链表中非常常用的算法。它基于运动学和直觉的基本定律。本文旨在向您简要介绍该算法,并帮助您了解这个看似神奇的算法。 假设高速公路上有两辆车。其中一辆的速度为 x,另一辆的速度为 2x。它们唯一能相遇的条件是它们…

[MYSQL] MYSQL表的操作

前言 由图可以看出,表是库的一部分,所以有库才能使用表 show databases; 查看已有的库 create database db_name ; 创建库 使用 use bd_name 使用库,之后对标进行增删查改就只会操作这个库里的而不影响其他库 创建表 create table [if not exists] table_name( d…

MySQL周内训参照3、简单查询与多表联合复杂查询

基础查询 1、查询用户信息&#xff0c;仅显示用户的姓名与手机号&#xff0c;用中文显示列名。中文显示姓名列与手机号列 SELECT user_id AS 编号, phone AS 电话 FROM user; 2. 根据订购表进行模糊查询&#xff0c;模糊查询需要可以走索引&#xff0c;需要给出explain语句。…

【区间动态规划】1771. 由子序列构造的最长回文串的长度

本文涉及知识点 动态规划汇总 LeetCode1771. 由子序列构造的最长回文串的长度 给你两个字符串 word1 和 word2 &#xff0c;请你按下述方法构造一个字符串&#xff1a; 从 word1 中选出某个 非空 子序列 subsequence1 。 从 word2 中选出某个 非空 子序列 subsequence2 。 连…

企业AI落地的大法器-用数据清洗手段提升数据质量,找回遗珠之光

开篇 书接上文&#xff0c;在上文《谈LORA微调与数据质量处理之争》中我们详细叙述了&#xff1a;LORA微调手段和数据清洗之分&#xff0c;以及如何平衡和组合使用LORA微调与数据清洗的手法。 文末我们提到了“下一篇我们讲着重讲述&#xff1a;在打造企业数据清洗工具、平台…

003 SpringBoot操作ElasticSearch7.x

文章目录 5.SpringBoot集成ElasticSearch7.x1.添加依赖2.yml配置3.创建文档对象4.继承ElasticsearchRepository5.注入ElasticsearchRestTemplate 6.SpringBoot操作ElasticSearch1.ElasticsearchRestTemplate索引操作2.ElasticsearchRepository文档操作3.ElasticsearchRestTempl…

java基于ssm+jsp 弹幕视频网站

1前台首页功能模块 弹幕视频网站&#xff0c;在弹幕视频网站可以查看首页、视频信息、商品信息、论坛信息、我的、跳转到后台、购物车、客服等内容&#xff0c;如图1所示。 图1前台首页界面图 登录&#xff0c;通过登录填写账号、密码等信息进行登录操作&#xff0c;如图2所示…

显卡GTX与RTX有什么区别?哪一个更适合玩游戏?

游戏发烧友们可能对游戏显卡并不陌生&#xff0c;它直接关系到游戏画面的流畅度、细腻程度和真实感。在众多显卡品牌中&#xff0c;英伟达的GTX和RTX系列显卡因其出色的性能而备受关注。 一、GTX与RTX的区别 架构差异 GTX系列显卡采用的是Pascal架构&#xff0c;这是英伟达在…

探索MySQL核心技术:理解索引和主键的关系

在数据密集型应用中&#xff0c;数据库的性能往往是决定一个应用成败的重要因素之一。其中&#xff0c;MySQL作为一种开源关系型数据库管理系统&#xff0c;以其卓越的性能和丰富的功能被广泛应用。而在MySQL数据库优化的众多技巧中&#xff0c;索引和主键扮演着极其重要的角色…

安霸CVFlow推理开发笔记

一、安霸环境搭建&#xff1a; 1.远程172.20.62.13 2. 打开Virtualbox&#xff0c;所在目录&#xff1a;E:\Program Files\Oracle\VirtualBox 3. 配置好ubuntu18.04环境&#xff0c;Ubuntu密码&#xff1a;amba 4. 安装toolchain&#xff0c;解压Ambarella_Toolchain_CNNGe…

鸿蒙开发HarmonyOS NEXT (二) 熟悉ArkUI

一、构造函数 构造一个商品类Item&#xff0c;然后利用foreach函数循环渲染 class Item {name: stringimage: ResourceStrprice: numberdiscount: numberconstructor(name: string, image: ResourceStr, price: number, discount: number 0) {this.name name;this.image ima…

JAVA进阶学习09

文章目录 一、双列集合Map1.1 双列集合介绍1.2 双列集合Map常见API1.3 Map集合遍历方式1.3.1 通过集合的全部键来遍历集合1.3.2 Map集合遍历方式21.3.3 Map集合遍历方式3 二、Map集合的实现类2.1 HashMap类2.2 LinkedHashMap2.3 TreeMap 三、可变参数四、Collections类五、集合…

恭喜朱雀桥的越南薇妮她牌NFC山竹汁饮料,成为霸王茶姬奶茶主材

朱雀桥NFC山竹汁饮料&#xff1a;荣登霸王茶姬奶茶主材&#xff0c;非遗传承的天然之选 近日&#xff0c;据小编了解到&#xff1a;霸王茶姬欣喜地宣布&#xff0c;成功与朱雀桥达成合作越南薇妮她VINUT牌NFC山竹汁饮料。这款商超产品凭借其卓越的品质与独特的口感&#xff0c…

PostgreSQL安装教程及文件介绍

Ubuntu 安装和配置 PostgreSQL 以 Ubuntu Server 20.04&#xff0c;PostgreSQL 12 版本为例。 1. 安装 使用如下命令&#xff0c;安装指定版本的 PostgreSQL sudo apt install postgresql-12在 Ubuntu 20.04 中安装 PostgreSQL 登录您的 Ubuntu 系统并使用以下 apt 命令更新…

Java web应用性能分析之【prometheus监控指标体系】

Java web应用性能分析之【系统监控工具prometheus】_javaweb服务器性能监控工具-CSDN博客 Java web应用性能分析之【prometheusGrafana监控springboot服务和服务器监控】_grafana 导入 prometheus-CSDN博客 因为篇幅原因&#xff0c;前面没有详细说明Prometheus的监控指标&…