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

15. 三数之和(实际是双指针类型的题目)

15. 三数之和

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 < = n u m s . l e n g t h < = 3000 3 <= nums.length <= 3000 3<=nums.length<=3000
  • − 1 0 5 < = n u m s [ i ] < = 1 0 5 -10^5 <= nums[i] <= 10^5 105<=nums[i]<=105

思路:

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

把符合条件的三元组放进[][]int切片中,然后再去重,这样是非常费时的,很容易超时。

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

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

双指针

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

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

拿这个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 就向右移动,才能让三数之和大一些,直到leftright相遇为止。

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

去重逻辑的思考

a的去重

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

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

但这里有一个问题,是判断 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,那这组数据{-1, -1 ,2}pass了,但实际上{-1, -1 ,2}是符合和为0的一个三元组。

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

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

那么应该这么写:

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

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

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

b与c的去重

bc也要去除重复的组合 例如【-1,0,1,1,2】,那么【-1,0,1】就是一个结果,而第二个1得到的还是【-1,0,1】,故让left++, 让b跳过第二个1

for left < right {if base + nums[left] + nums[right] == 0 {res = append(res,[]int{base,nums[left],nums[right]})// 上面找到一个组合后,以nums[i]作为基数的可能还有其他组合,此时移动left和right//同样,left和right也要去除重复的组合  例如【-1,0,1,1,2】,那么【-1,0,1】就是一个结果,而第二个1得到的还是【-1,0,1】,故让left++,跳过第二个1for left < right && nums[left + 1] == nums[left] { left++ }for left < right && nums[right - 1] == nums[right] { right-- }//当上面两个for结束时,说明num[left+1] != nums[left],nums[right-1]!=nums[right],此时我们要取的就是left+1和right-1的那两个数,即如下left++right--} else if base + nums[left] + nums[right] < 0 {left++} else {right--}
}

当然也可以用回溯法,找出所有符合条件的三元组,但是回溯法和暴力法直接三层遍历一样,时间复杂度是 O ( n 3 ) O(n^3) O(n3)

Go代码

func threeSum(nums []int) [][]int {/*对切片先进行排序,方便去除重复的结果,然后固定一个数,移动另外两个数*/if nums == nil { return nil}var res [][]intsort.Ints(nums)//由于是三个数之和,而另外的两个数都是取i之后的,因为取i之前的情况的三个数的组合已经包含在i还较小时作为固定数的循环中了// 从头至尾以当前数字作为固定的数字,至少需要三个数字,所以遍历到len(nums)-2即可for i := 0;i < len(nums) - 2;i++ {if i > 0 && nums[i] == nums[i - 1] {continue} // 对基数去除重复的情况base := nums[i] // 固定一个基数left ,right := i + 1, len(nums) - 1 // 双指针的两个数for left < right {if base + nums[left] + nums[right] == 0 {res = append(res,[]int{base,nums[left],nums[right]})// 上面找到一个组合后,以nums[i]作为基数的可能还有其他组合,此时移动left和right//同样,left和right也要去除重复的组合  例如【-1,0,1,1,2】,那么【-1,0,1】就是一个结果,而第二个1得到的还是【-1,0,1】,故让left++,跳过第二个1for left < right && nums[left + 1] == nums[left] { left++ }for left < right && nums[right - 1] == nums[right] { right-- }//当上面两个for结束时,说明num[left+1] != nums[left],nums[right-1]!=nums[right],此时我们要取的就是left+1和right-1的那两个数,即如下left++right--} else if base + nums[left] + nums[right] < 0 {left++} else {right--}}}return res   
}

在这里插入图片描述


http://www.mrgr.cn/news/28414.html

相关文章:

  • 【Hadoop】【hdfs】【大数据技术基础】实验三 HDFS 基础编程实验
  • Go语言中的复合类型赋值:简化你的初始化过程
  • 微搭低代码入门05循环
  • 怎么样绑定域名到AWS(亚马逊云)服务器
  • Openshift 如何更新访问控制机
  • 探索美赛:从准备到挑战的详细指南
  • 支持升降压型、升压、降压、60V的1.2MHz频率LED恒流驱动器LGS63040、LGS63042
  • C/C++实现植物大战僵尸(PVZ)(打地鼠版)
  • 配置cobbler服务提供centos7安装源
  • [OpenCV] 数字图像处理 C++ 学习——15像素重映射(cv::remap) 附完整代码
  • 初中生物--5.单细胞生物
  • 大数据新视界 --大数据大厂之MongoDB与大数据:灵活文档数据库的应用场景
  • 建设世界一流财务管理体系【数字化顶层设计】【持续更新】
  • 大学生看过来,必备4款写论文AI写作网站先稿后付
  • 【AI小项目5】使用 KerasNLP 对 Gemma 模型进行 LoRA 微调
  • 开题报告的流程
  • Go语言开发im-websocket服务和vue3+ts开发类似微信pc即时通讯
  • 背包问题(如何定义dp状态)
  • CSS调整背景
  • 绝缘检测原理
  • C语言代码练习(第二十五天)
  • 拖拽排序的实现示例demo
  • 系统优化工具 | TweakPower v2.0.6 绿色版
  • 【C语言学习路线】
  • 【C++篇】C++类与对象深度解析(二):类的默认成员函数详解
  • JUC学习笔记(一)