很有意思的一道题,值得好好思考,虽然难度只有Mid,但是个人觉得不比Hard简单

题目描述

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 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
  • -105 <= nums[i] <= 105

解答

解法一 排序+双指针+三循环

很容易想到的是先对序列进行排序,首尾两个指针,两个指针中间采取遍历,三个循环查找所有满足条件的答案。

时间复杂度为\(O(n^3)\) 空间复杂度为 \(O(n)\)

然后一开始就疯狂说超时,结果一番修改,终于不报超时了。

class Solution {

    public List<List<Integer>> threeSum(int[] nums) {
int len = nums.length;
// 对数组进行排序
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 定义一个指针,指向数组的头部,不断的去遍历每一个不重复的元素
// 每次遍历中再定义一个指针指向尾部,不断往前遍历,判断是否存在合理的三元组
for (int pi = 0; pi < len - 2; pi++) {
// 很明显如果pi对应的值都大于0了,说明后面的全是大于0,已无解了
if (nums[pi] > 0) {break;}
for (int pj = len - 1; pj > pi; pj--){
// 这里的pj值小于0,也是一样的,代表这一轮已无解了直接break
if (nums[pj] < 0) {break;} for (int pz = pj - 1; pz > pi; pz--) { if (nums[pi] + nums[pj] + nums[pz] > 0) { continue; }
if (nums[pi] + nums[pj] + nums[pz] < 0) { break; } if (nums[pi] + nums[pj] + nums[pz] == 0){
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[pi]);
temp.add(nums[pz]);
temp.add(nums[pj]);
ans.add(temp);
// 找到就退出此循环
break;
}
// 走到最后一个相同元素的位置
while (pz > 0 && nums[pz] == nums[pz - 1]) {pz--;}
}
// 走到最后一个相同元素的位置
while (pj > 0 && nums[pj] == nums[pj - 1]) {pj--;}
}
// 走到最后一个相同元素的位置
while (pi < len - 1 && nums[pi] == nums[pi + 1]) {pi++;}
}
return ans;
}
} // 执行用时: 2164 ms
// 内存消耗: 42.3 MB

不报超时了,但是时间大概就比别人多了很多很多吧

果然时间复杂度太高了,但是想了半天也不知道是为什么,然后去看教程,教程说能将三循环优化成双循环。

一看代码还是三循环,人傻了反正是。

虽然没懂为什么for改成while起作用了,但是直接动手改写我最后的一层循环。

写着写着就悟了。。。果然实践出真知

直接上代码吧

解法二 排序+双指针+双循环

时间复杂度\(O(n^2)\)

class Solution {

    public List<List<Integer>> threeSum(int[] nums) {
int len = nums.length;
// 对数组进行排序
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 定义一个指针,指向数组的头部,不断的去遍历每一个不重复的元素
// 每次遍历中再定义一个指针指向尾部,不断往前遍历,判断是否存在合理的三元组
for (int pi = 0; pi < len - 2; pi++) {
// pi指向的元素如果大于0,后面无解,直接返回
if (nums[pi] > 0) {return ans;}
int pz = pi + 1;
for (int pj = len - 1; pj > pi; pj--){
// pj指向的元素如果都小于0,说明剩余区域全部为零了,直接break
// pz指向大于pj时说明无解了
if (nums[pj] < 0 || pz >= pj) {break;}
// 更新pz
while (pz < pj && nums[pi] + nums[pj] + nums[pz] < 0) {
pz++;
}
// 记录pz如果合理
if (pz < pj && nums[pi] + nums[pj] + nums[pz] == 0){
List<Integer> temp = new ArrayList<Integer>();
temp.add(nums[pi]);
temp.add(nums[pz]);
temp.add(nums[pj]);
ans.add(temp);
}
// 走到最后一个相同元素的位置
while (pj > 0 && nums[pj] == nums[pj - 1]) {pj--;}
}
// 走到最后一个相同元素的位置
while (pi < len - 1 && nums[pi] == nums[pi + 1]) {pi++;}
}
return ans;
}
} // 执行用时: 24 ms
// 内存消耗: 42.8 MB

这速度反正快了很多很多吧

why?

首先说明,数组按照从小到大的顺序排序,

我们最外层的循环是遍历指向数组头的指针,Head

第二层的循环是遍历指向数组尾的指针,Tail

第三层的循环是两个首尾指针中间的区域,Mid

对于一个Head,我们的尾指针Tail 将会从最后一个元素慢慢往前遍历至头元素的前一个元素。

假如此时 Head = \(n\), Tail = \(m\),\(n<m\),如果找到一个Mid =\(x\), \(n<x<m\)

满足条件 \(nums[n] + nums[x] +nums[m] = 0\)

那么此时Tail更新为\(m-1\),那么我们第三层循环的Mid指针需要重新从\(n+1\)开始遍历吗?

答案是不需要。

为什么?

此时有 \(nums[n] + nums[x] +nums[m] = 0, nums[m-1]<nums[m]\)

所以不难推出 \(nums[n]+nums[x]+nums[m-1]<0\)

因此\(x\)左边的值都只会使不等式小于0,我们只需要从\(x\)往右开始遍历Mid就可以了。

说到这里,可以发现Mid指针,在Head指针固定的情况下,只会更新小于len次,len为数组的长度。

也就是说当Head=\(n\)时,

Mid指针只会遍历小于\(length(nums)-n\)次,

而Tail指针,也是遍历小于\(length(nums)-n\)次

分配下来,Tail的一次循环Mid更新的次数是常数项。而在某些时刻Mid会直接大于Tail,就直接退出内层循环。

因此第三层循环虽然是存在,但是在第二层循环的指针固定的情况下,只会循环常数项次,至此第三层循环由\(O(n)\)被优化至\(O(1)\)

leetcode 刷题(数组篇)15题 三数之和 (双指针)的更多相关文章

  1. LeetCode 16. 3Sum Closest(最接近的三数之和)

    LeetCode 16. 3Sum Closest(最接近的三数之和)

  2. [LeetCode] 15. 3Sum 三数之和

    Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all un ...

  3. 【LeetCode】15、三数之和为0

    题目等级:3Sum(Medium) 题目描述: Given an array nums of n integers, are there elements a, b, c in nums such t ...

  4. 【LeetCode】15. 3Sum 三数之和

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人公众号:负雪明烛 本文关键词:3sum, 三数之和,题解,leetcode, 力扣,P ...

  5. C#LeetCode刷题之#16-最接近的三数之和(3Sum Closest)

    目录 问题 示例 分析 问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3620 访问. 给定一个包括 n 个整数的 ...

  6. [leetcode]15. 3Sum三数之和

    Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find ...

  7. LeetCode第[15]题(Java):3Sum (三数之和为目标值)——Medium

    题目难度:Medium 题目: Given an array S of n integers, are there elements a, b, c in S such that a + b + c  ...

  8. Leetcode(15)-三数之和

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. ...

  9. 【LeetCode 15】三数之和

    题目链接 [题解] 先把n个数字升序排个序. 然后枚举三元组最左边的那个数字是第i个数字. 之后用两个指针l,r移动来获取三元组的第2个和第3个数字. (初始值,l=i+1,r = n-1); 如果a ...

  10. leecode第十六题(最接近的三数之和)

    class Solution { public: void quick_order(vector<int>& num, int star, int en)//快排 { int st ...

随机推荐

  1. NGK全球巡回路演莫斯科站,共探BGV能否超越YFI?

    近日,NGK全球巡回路演在俄罗斯首都莫斯科落下帷幕.此次路演取得了空前的成功.路演伊始俄罗斯路演讲师Andrew致开幕辞,安德鲁称,俄罗斯作为未一个幅员辽阔的大国,区块链技术有着非常大的应用场景. 俄 ...

  2. 精密进近OCH的计算

    一.计算步骤 以I类精密进近为例,运行标准的制定大致分为以下几个步骤: 1)确定精密航段的超高障碍物. 2)计算当量高 3)计算高度损失 4)当量高与高度损失相加得到超障高OCH 5)对复飞段障碍物进 ...

  3. 嵌入式开发板使用网口和nfs进行文件共享

    如果你的开发板有网口,类似于这玩意. 那么,你可以去买根网线,类似于这玩意. 然后你就可以将你的电脑和开发板用网线连起来,通过nfs(网络文件系统)来进行文件夹共享,文件夹共享就相当于挂载,nfs是利 ...

  4. vue_webpack

    1.生成项目工程描述文件 npm init 2.安装webpack开发依赖 (本地安装):npm install -D 3.(webpack4.0版本以上安装webpack cli) npm inst ...

  5. Vue学习笔记-nodejs+vue-cli4+webpack按装配置+项目创建

    一  使用环境: windows 7 64位操作系统 二  Vue学习-nodejs按装配置,Node.js 就是运行在服务端的 JavaScript. 1. 去nodejs的官网下载  https: ...

  6. Java流程控制:选择结构

    一.选择结构 选择结构用于判断给定的条件,根据判断的结果来控制程序的流程. Java中选择结构的语法主要分为'if...else'语句和'switch...case'语句. Java中选择结构语句在语 ...

  7. Vue项目的创建、路由、及生命周期钩子

    目录 一.Vue项目搭建 1.环境搭建 2.项目的创建 3.pycharm配置并启动vue项目 4.vue项目目录结构分析 5.Vue根据配置重新构建依赖 二.Vue项目创建时发生了什么 三.项目初始 ...

  8. msfconsole 常用命令记录

    Metasploit是一款开源的渗透测试框架,它现在还在逐步发展中,下面介绍的一些功能和命令,可能会在未来失效. Metasploit框架提供了多种不同方式的使用接口: msfgui msfconso ...

  9. 解决springboot项目打成jar包部署到linux服务器后上传图片无法访问的问题

    前言:目前大三,自己也在学习和摸索的阶段.在和学校的同学一起做前后端分离项目的时候,我们发现将后端打包成jar,然后部署到服务器中通过java -jar xxx.jar运行项目以后,项目中存在文件上传 ...

  10. PAT-1086(Tree Traversals Again)Java语言实现+根据中序和前序遍历构建树并且给出后序遍历序列

    Tree Traversals Again Tree Traversals Again 这里的第一个tip就是注意到非递归中序遍历的过程中,进栈的顺序恰好是前序遍历的顺序,而出栈的顺序恰好是中序遍历的 ...