当前位置: 首页 > news >正文

L37.【LeetCode题解】三数之和(双指针思想)

目录

1.题目

2.分析

算法

方法1:使用set去重

版本1代码

方法2:利用数组的有序性来去重

操作left和right

操作bound

其他问题

1.如果和<0,left++后需要去重吗? 同理,如果和>0,right--后需要去重吗?

2.如果和==0,是只移动left还是只移动right还是两个指针都移动?

如果只移动其中任何一个指针

如果一次性移动两个指针

版本2代码

版本2代码的小优化

3.验证指针移动的效率问题

和为0时移动动两个指针

只移动其中任何一个指针


1.题目

https://leetcode.cn/problems/1fGaJU/description/

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

示例 1:

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

示例 2:

输入:nums = []
输出:[]

示例 3:

输入:nums = [0]
输出:[]

提示:

  • 0 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

注意:本题与主站 15 题相同:15. 三数之和 - 力扣(LeetCode)

2.分析

算法

从L36.【LeetCode题解】查找总价格为目标值的两个商品(剑指offer:和为s的两个数字) (双指针思想,内含详细的优化过程)文章得到的经验来看,显然先排序,利用有序数组的单调性使用三指针:将其中一个指针bound固定不动,使用左右指针left和right来移动,如果满足题目条件,就将{nums[left],nums[right],nums[bound]}尾插到类型为vector<vector<int>> 的ret中

移动的方法:

1.如果nums[left]+nums[right]+nums[bound]<0,需要增大结果,left++

2.如果nums[left]+nums[right]+nums[bound]<0,需要减小结果,right--

但题目还要求了三元组不能重复,所以需要对返回的结果进行去重操作

★注意题目的细节:0 \leq nums.length \leq 3000,nums.length可以为0,直接返回{ },同理nums.length为1或2时,也直接返回{ },这个单独处理

但https://leetcode.cn/problems/3sum/题的nums.length最小取3,这里要注意

方法1:使用set去重

之前在CC42.【C++ Cont】STL库的红黑树set的使用文章提到过:

"set与multiset 的区别:set不能相同元素,multiset可以存相同的元素,其余的使用方式完全一致.因此,可以用set来给数据去重"

版本1代码
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {set<vector<int>> tmp;vector<vector<int>> ret;if (nums.size()==0)return ret;sort(nums.begin(),nums.end());for (int bound=nums.size()-1;bound>1;bound--){int left=0;int right=bound-1;while (left<right){int sum=nums[left]+nums[right]+nums[bound];if (sum>0)right--;else if (sum<0)left++;else//sum==0{if (tmp.count({nums[left],nums[right],nums[bound]})==0){tmp.insert({nums[left],nums[right],nums[bound]});ret.push_back({nums[left],nums[right],nums[bound]});} left++;   //break;}}}return ret;}
};

提醒:注意到break;语句被注释掉了,这里找到一个不能直接退出循环! 需要找到所有满足条件的三元组

提交结果:

*面试时不建议使用此法来去重,没有达到训练的要求

方法2:利用数组的有序性来去重

例如给定以下数组[-1,0,1,2,1,-4],排过序后为[-4,-1,0,1,1,2]

操作left和right

以下两种情况均满足要求:

显然需要去重,right前后都是-1,需要跳过重复的情况,可以比较right前后的值是否相同,使用循环来跳过所有重复的情况,因为重复的元素都是在一起的,有以下代码:

right--;
//right--后,right之前为right+1,比较--前和--后right指向的值是否相同
while(nums[right]==nums[right+1])right--;

但这样写会有潜在的问题:right可能会越过left导致left<=right,甚至是对数组越界访问,因此还需要一个条件left>right

right--;
while(nums[right]==nums[right+1] && left<right)right--;

同理left++后也有可能指向相同的值,代码如下:

left++;
while(nums[left]==nums[left-1] && left<right)left++;

left>right这个思想曾经在120.【C语言】数据结构之快速排序(详解Hoare排序算法)文章中提到过的单趟排序的代码,内循环由两个条件限制:

	//单趟快速排序int key_i = left;while (left < right){//由于key_i==left,因此right指针先走//右边找小while (left < right && arr[right] >= arr[key_i]){right--;}//左边找大while (left < right && arr[left] <= arr[key_i]){left++;}Swap(&arr[left], &arr[right]);}Swap(&arr[key_i], &arr[left]);
操作bound

注意bound++后也有可能指向和之前相同的值,因此也要对bound去重,这里容易忽视

其次bound不能一直++,也有可能越界,bound的最大取值为 nums.size()-3,要为nums[left]和nums[right]留空位

bound++;
while(nums[bound-1]==nums[bound] && bound<nums.size()-2)bound++;
其他问题
1.如果和<0,left++后需要去重吗? 同理,如果和>0,right--后需要去重吗?

答:去重指的是:在三元组和为0的情况下且至少有两个三元组的元素相同时,需要去重,前提是和为0,如果>0或<0,不需要去重

2.如果和==0,是只移动left还是只移动right还是两个指针都移动?

从正确性上来说都没有问题,下面进行分析:

如果只移动其中任何一个指针

这里以left为例子:因为要去重,所以要移动到和之前指向的值有所不同的位置,当left移动到新的位置时,和必然不为0,可以交给下一次循环时判断并移动指针

以[-5,0,1,1,2,2,3,3,4,4]为例分析: 

 

如果一次性移动两个指针

仍然以[-5,0,1,1,2,2,3,3,4,4]为例分析:

感觉上一次性移动两个指针效率比只移动其中任何一个指针要高一点,在文章的最后会验证此想法

版本2代码
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {if (nums.size()<3)return {};sort(nums.begin(),nums.end());vector<vector<int>> ret;int left,right,bound=0;while(bound<nums.size()-2){left=bound+1;right=nums.size()-1;while (left<right){if (nums[bound]+nums[left]+nums[right]>0)right--;             else if (nums[bound]+nums[left]+nums[right]<0)left++;else{ret.push_back({nums[bound],nums[left],nums[right]});left++;while(nums[left]==nums[left-1]&&left<right)left++;right--;while(nums[right]==nums[right+1] && left<right)right--;}}bound++;while(nums[bound-1]==nums[bound]&&bound<nums.size()-2)bound++;}return ret;}
};

提交结果:

版本2代码的小优化

当nums[bound]>0时可以直接退出循环,一定找不到符合要求的三元组,直接返回即可,此操作可以加在循环的最后:

while(bound<nums.size()-2)
{//......while (left<right){if (nums[bound]+nums[left]+nums[right]>0)right--;             else if (nums[bound]+nums[left]+nums[right]<0)left++;else{//......}}bound++;while(nums[bound-1]==nums[bound]&&bound<nums.size()-2)bound++;if (nums[bound]>3)return ret;
}

3.验证指针移动的效率问题

可以写个测试程序:统计三个指针bound、left和right的移动次数:

到leetcode上获取数据量大的测试用例,由于在leetcode上提交时网站会报告没有通过的测试用例,因此可以通过写条件判断:指定获取150个元素以上的测试用例:

由于测试代码较大,这里给出下载链接,提取码: hhrmhttps://pan.baidu.com/s/1i5juwMMvY4SXrqT-OOWKOw?pwd=hhrm

和为0时移动动两个指针

运行结果:

只移动其中任何一个指针

运行结果:

其实没有区别,和为0时指针移动的次数是一样的,都是4461737次

相关文章:

  • Java练习——day2(集合嵌套)
  • Nginx:轻量级高性能的Web服务器与反向代理服务器
  • 开源推荐#6:可爱的临时邮箱服务
  • 模型提示词
  • Ubuntu源码制作openssh 9.9p2 deb二进制包修复安全漏洞 —— 筑梦之路
  • 基于.NET后端实现图片搜索图片库 核心是计算上传图片与库中图片的特征向量相似度并排序展示结果
  • [Jenkins]pnpm install ‘pnpm‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。
  • Harmonyos-Navigation路由跳转
  • ios app的ipa文件提交最简单的方法
  • 论文阅读笔记:Generative Modeling by Estimating Gradients of the Data Distribution
  • 云钥科技柔性上料振动蜘蛛手工作原理及应用范围详细介绍
  • 盈达科技GEO技术体系全景解密:AIM³ Pro × AICC × GEO-BENCH Pro构建全球认知堡垒​
  • 计算机网络 应用层
  • spring boot -- 配置文件application.properties 换成 application.yml
  • Spring Boot 实现防盗链
  • 智能语音处理+1.4语音合成之输出英文音频文件(100%教会)
  • 华为HCIE-openEuler认证:能否成为国产操作系统领域的技术稀缺人才?
  • MySQL数据库表查询
  • mitmproxy 一款强大的 HTTP(S) 拦截与调试工具
  • openGauss DataVec + Dify,快速搭建你的智能助手平台
  • 香港服务器做网站/百度推广账号出售
  • 莱特币做空网站/最大的推广平台
  • 动态网站开发商城网站/google adwords
  • 网站建设公司3lue/东莞网站推广企业
  • 株洲seo优化排名/seo网站优化
  • 如何设置网站关键字/站长工具无内鬼放心开车禁止收费