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

(leetcode) 力扣100 6.三数之和 (双指针)

题目

在这里插入图片描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

数据范围

3 <= nums.length <= 3000
-105 <= nums[i] <= 105

样例

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

题解

博主第一版代码(失败,博主会在思路记录错误的心路历程,防止再犯,有兴趣的盆友可以引以为戒)

public static List<List<Integer>> threeSum(int[] nums) {//排序数组Arrays.sort(nums);//存储最终结果List<List<Integer>> list=new LinkedList<>();int left=0;int len=nums.length;int right=len-1;while(left<right){List<Integer> tlist=new LinkedList<>();//首先确定左右指针之和,然后确定寻找方向,从而达到减少遍历次数的效果int temp=nums[left]+nums[right];if(temp<=0){int t=right-1;while(nums[t]+temp>=0&&t>left){//重复跳过if(t!=right-1&&nums[t]==nums[t+1]){t--;continue;}if(nums[t]+temp==0){tlist.add(nums[left]);tlist.add(nums[t]);tlist.add(nums[right]);list.add(tlist);}t--;}left++;while(nums[left]==nums[left-1]&&(left+1)<right)left++;}else{int t=left+1;while(nums[t]+temp<=0&&t<right){if(t!=left+1&&nums[t]==nums[t-1]){t++;continue;}if(nums[t]+temp==0){tlist.add(nums[left]);tlist.add(nums[t]);tlist.add(nums[right]);list.add(tlist);}t++;}right--;while(nums[right]==nums[right+1]&&(right-1)>left)right--;}}return list;}

博主第二套代码

public static List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);List<List<Integer>> list = new LinkedList<>();int len = nums.length;for (int i = 0; i < len - 2; i++) {// 重复跳过if (i > 0 && nums[i] == nums[i - 1]) continue;int left = i + 1;int right = len - 1;while (left < right) {int sum = nums[i] + nums[left] + nums[right];if (sum == 0) {list.add(Arrays.asList(nums[i], nums[left], nums[right]));// 重复跳过while (left < right && nums[left] == nums[left + 1]) left++;// 重复跳过while (left < right && nums[right] == nums[right - 1]) right--;left++;right--;} else if (sum < 0) {left++;} else {right--;}}}return list;
}

官方题解

class Solution {public List<List<Integer>> threeSum(int[] nums) {int n = nums.length;Arrays.sort(nums);List<List<Integer>> ans = new ArrayList<List<Integer>>();// 枚举 afor (int first = 0; first < n; ++first) {// 需要和上一次枚举的数不相同if (first > 0 && nums[first] == nums[first - 1]) {continue;}// c 对应的指针初始指向数组的最右端int third = n - 1;int target = -nums[first];// 枚举 bfor (int second = first + 1; second < n; ++second) {// 需要和上一次枚举的数不相同if (second > first + 1 && nums[second] == nums[second - 1]) {continue;}// 需要保证 b 的指针在 c 的指针的左侧while (second < third && nums[second] + nums[third] > target) {--third;}// 如果指针重合,随着 b 后续的增加// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环if (second == third) {break;}if (nums[second] + nums[third] == target) {List<Integer> list = new ArrayList<Integer>();list.add(nums[first]);list.add(nums[second]);list.add(nums[third]);ans.add(list);}}}return ans;}
}作者:力扣官方题解
链接:https://leetcode.cn/problems/3sum/solutions/284681/san-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode

思路

这道题花了博主很多时间,这当然有博主算法功底不够扎实的问题,但这道题确实需要下一些功夫,如何做到降低时间复杂度,降低复杂度后,如果做到去重,在第一次面对这类题时,处理细节确实会花费不少时间。 废话不多说,首先说说博主第二版代码的思路。

三套代码,无论是哪一个,都是使用的排序加双指针的思路,因为这道题如果直接遍历,在O(N3)的情况下,计算次数最少也是109 级别,已经远远超过了需求(c语言最差尽量维持在108 ,java需要更高要求)。我们再观察数据,数据量最大只有3000,那么我们在处理原数组前快排一次,不仅对最后算法时间没有任何影响,并且更方便处理数据, 同时也更好实现题目中提到的不出现重复结果这一要求。

接下来我们进一步思考,如何缩短时间复杂度,数据量为3000,那么我们的目标就为将时间复杂度处理到O(N2)及其以下,再根据题意,我们需要做的操作是遍历数组找到三个和为0的数,那么正好有一个思路可以将时间复杂度的次方-1,那就是双指针。

当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N2) 减少至 O(N)。

O(N3)同理,在确定一个数a之后,我们需要遍历的两个数要求就是b+c=-a,但因为我们已经排序数组,当b的数值上升时,要满足结果为-a不变那么一定需要c的数值下降,完全满足上面加粗字体的要求。

思路确定,按照思路书写代码即可,代码难度不高,但要写好代码却也不容易,代码需要注意细节,跳过重复的情况不要忘记,如何使用双指针也是一个细节,这也是导致博主第一版代码遗憾退场的原因,第一版代码的问题博主放在最后与大家讨论,不过多浪费大家精力,有兴趣的盆友可以看看。

官方题解,整体思路与博主的一致,但写法上略微不同,官方题解更像是两重循环遍历,在第二重循环遍历中使用了双指针思想,把原本的第二重和第三重遍历结合到了这一重中。而博主的第二版代码中将第二重的遍历彻底写成了双指针的标准模版形式。

具体差别在于官方的方法可以理解为定两点,动一点,而博主的思路是定一点,动两点。两种都没问题,大家凭借个人的代码习惯学习即可。

接下来是博主第一版代码的错误总结,大家没兴趣可以直接跳过

博主的第一版代码犯了一个大忌,博主的第一版代码没有选择先去定点,而是直接在双指针模版里再去找一个点,这样处理逻辑难度思维难度提升的同时,还会造成一些细节的情况不好处理。

博主想先对左右指针的和进行判断,根据具体的值来确定接下来从什么方向找到第三个符合条件的数。最后再给根据这个和来移动左右指针。例如l与r的和为5那么肯定要找小于0的数,第三个数就从l+1开始寻找,这样就能减少遍历次数。但在遍历完成,指针的移动犯了难,我的思路是,在遍历完毕后,对右指针进行左移(因为原左右指针和大于0,右指针左移才能跟方便找到等于0的三个数),但这个思路漏洞很大,会产生缺失,所以大家也不用具体思考我的思路,只需要记住,在使用双指针减少时间复杂度时,最好不要把双指针放在最外层再定数

相关文章:

  • 《普通逻辑》学习记录——关系命题及其推理
  • 《深入理解分布式系统》之认识分布式系统
  • C语言| 递归求1+2+...+100的和
  • Ragflow服务器上部署教程
  • 已经写好论文的AI率降低
  • VTK|结合qt创建通用按钮控制显隐(边框、坐标轴、点线面)
  • 嵌入式学习--江协51单片机day1
  • 【HDLBits刷题】Verilog Language——1.Basics
  • 代码随想录算法训练营总结篇
  • Kubernetes弹性伸缩:让应用自动应对流量洪峰与低谷
  • 购物|电商购物小程序|基于微信小程序的购物系统设计与实现(源码+数据库+文档)
  • OpenKylin安装Elastic Search8
  • k8s node 内存碎片化如何优化?
  • 文件上传漏洞篇:upload-labs靶场搭建
  • Ubuntu 系统中解决 Firefox 中文显示乱码的完整指南
  • 代码随想录算法训练营第五十六天| 图论2—卡码网99. 岛屿数量(dfs bfs)
  • 养生融入生活,畅享健康人生
  • MySQL8查询某个JSON类型的字段中出现过的所有键名(json key name)并去重返回
  • conda虚拟环境相关操作
  • 第三章:langchain加载word文档构建RAG检索教程(基于FAISS库为例)
  • 国务院安委会办公室印发通知:坚决防范遏制重特大事故发生
  • 甘肃省政府原党组成员、副省长杨子兴被提起公诉
  • 马上评|子宫肌瘤惊现男性患者,如此论文何以一路绿灯?
  • 多地政府机关食堂五一“开门迎客”:怎么看这场“宠粉”大戏
  • 日本来信|劳动者的书信④
  • 2024年境内酒店住宿行业指标同比下滑:酒店行业传统增长模式面临挑战