三数之和

描述

给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。
注意:
三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
解集中不能包含重复的三元组。
例如,给定的数组 S = {-10 0 10 20 -10 -40},解集为(-10, -10, 20),(-10, 0, 10)
0 <= S.length <= 1000

示例1

输入:
[0]

返回值:
[]

示例2

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

返回值:
[[-2,0,2],[-2,1,1]]

示例3

输入:
[-10,0,10,20,-10,-40]

返回值:
[[-10,-10,20],[-10,0,10]]

思路

三数之和,最简单最暴力的解法就是 三重循环遍历,但是这样的时间复杂度太高了为O(n^3)。

大家看上图,其实咱们可以使用 三指针 的方式来解决这道题。

  • 将数组按照从小到大排序。
  • 先定义I、J、K 三指针,I 位于最左侧,J 位于 I 的右侧,K 位于 数组最右侧。
  • JK 分别向对方移动,当 I + J + K > 0 时,K 向左移动,反之 J 向右移动,沿途碰到符合 I + J + K = 0 记录下来。
  • JK 遍历完之后,I 向右移动,J 位于 I 的右侧,K 位于 数组最右侧。在重复上面的逻辑即可。
  • 这里面有个问题,那就是会有重复的组合,咱们遍历的时候如果碰到相邻元素相等时,可以直接跳过,这样能减少重复次数。
  • 还有一个跳出等其他细节,大家可以看下方代码注释。

AC 代码

public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
            if (num == null || num.length < 3) {
                return res;
            }
            // 对数组从小到大排序
            Arrays.sort(num);
            // 如果 排序后第一个(最小的)的元素 大于 0, 那说明这个数组不可能有等于 0 的三个元素。
            if (num[0] > 0) {
                return res;
            }
            int length = num.length;
            for (int i = 0; i < length; i ++) {

                // 如果相邻的两个元素相等,则直接跳过,防止重复
                if (i > 0 && num[i] == num[i - 1]) {
                    continue;
                }
                // k 是右侧指针
                int k = length - 1;
                // 这个等价于 int temp = 0 - num[i], 确定第一个元素后,去剩下的队列中寻找另外两个元素
                int temp = - num[i];
                // 从第一个元素后边开始遍历
                for (int j = i + 1; j < length; j ++) {
                    // 这个和上面一样,碰到相邻元素相等就跳过
                    if (j > i + 1 && num[j] == num[j - 1]) {
                        continue;
                    }
                    // 当右侧指针小于左侧指针,并两者之和大于 temp 时,右侧指针要左移,因为是从小到大排序的
                    while (k > j && num[k] + num[j] > temp) {
                        k --;
                    }
                    // 当 两个指针重合时,说明 b + c > -a ==> a + b + c > 0
                    // 就算是在往后遍历也只是 a + b + c 越来越大,永远不会 等于 0,因为数组是从小到大有序的
                    if (k == j) {
                        break;
                    }
                    if (num[k] + num[j] == temp) {
                        ArrayList<Integer> list = new ArrayList<Integer>();
                        list.add(num[i]);
                        list.add(num[j]);
                        list.add(num[k]);
                        res.add(list);
                    }

                }
            }
        return res;
    }
  • 时间复杂度:时间复杂度:O(N^2),N 为数组长度
  • 空间复杂度:O(log^N),这个事排序的空间复杂度。

最后

大家可以去 【牛客网-题库-在线编程】去练习一下。
可以去微信搜索:【蘑菇睡不着】交个朋友~
也可以扫描下方二维码。

图片说明

#Java开发##学习路径#
全部评论
emm用hash map就可以把暴力的时间复杂度降到n^2。遍历每两个数,用hash map判断这两数和的相反数是不是存在就好了。
点赞 回复 分享
发布于 2021-09-16 21:29

相关推荐

前情提要:上个帖子发了我提前10分钟进入会议室结果被硬控40分钟,无语发了条邮件给vivo邮箱,没想到还真给我回了,过了十几分钟给我打电话说是hr的问题,给我的链接和给面试官的链接不是同一个,我俩互相被硬控了40多分钟,给我道歉并问能否接受重新面试,可以立刻安排。我想着来都来了还是面一个吧,于是安排在4点15面了面试时长约45min,无手撕,基本拷打实习1.进会议室后面试官和我说明了一下情况,表示是他们这边的问题,不好意思。然后让我开始自我介绍。2.拷打实习比较擅长的中间件有哪些实习都干了些啥你说你用rabbitmq解耦消息通知,那业务里有没有需要保证消息顺序性的情况(我说没有,我接触到的模块没有需要保证消息顺序性的)那假设现在需要你负责一个模块,要保证消息顺序性,怎么保证(这一块答得不好,本以为自己已经背熟了但讲起来还是一坨,在面试官的提示下跌跌撞撞答出来)排行榜怎么实现的?哪里用到了缓存一致性(文章类的修改)问我旁路缓存的模式不能保证强一致性,为什么要选用这种方法确保文章的缓存一致性(从业务考虑出发,社区的文章不需要保证实时一致性,举了牛客修改文章之后得过一段时间才能重新看到自己修改后的内容的例子)追问可以理解为什么不需要保持强实时一致性,但旁路缓存也无法保证最终一致性,redis可能宕机,怎么解决这里我说可以用rabbitmq去重试,但面试官说也不能保证,不能redis宕机后一启动你就重试,业务上不可行。我说事务面试官笑笑说别忘了redis不怎么支持事务。后来慢慢引导到为队列里的信息配置ttl,redis宕机时数据可以通过死信队列存在别处,然后等流量低谷期手动补充丢失信息到redis里。不过我觉得这好像也不是很优雅的方案就是了看你用到了Threadlocal,讲一下用的时候有什么需要注意的?讲一下它的原理项目听我是学习类的微服务项目就没问,估计觉得不上线的微服务项目都是过家家。2.八股redis的zset底层怎么实现的,除了跳表还有什么?讲一下你知道的线程安全的类(我说vector,hashtable,concurrenthashmap,copyonrightArraylist感觉已经够了,结果让我再想,没办法憋出来个阻塞队列,说实在想不出来了)内部原理都知道吗?(阻塞队列不清楚,其他的都讲了,但发现concurrenthashmap其实记得不熟,说的磕磕绊绊的)3.闲聊平常都有什么爱好学校里的学习和实习和项目有什么区别(我这里疯狂吐槽学校落后,给他听困了,问还有吗,不需要这么局限。然后我转到学到要通过业务去思考问题,不要陷入技术死循环中,技术说到底是要为业务服务的,他突然来神了)怎么学习新知识的看过哪些源码反问:vivo的互联网业务有哪些,部门的业务呢(部门主要做广告引擎推送)点评一下面试表现,给一点建议?(这里有点意思,面试官说整体还可以,我调侃说听着像客套话,面试官笑着说那你要不要听嘛)说我表达和逻辑都挺好的,虽然有些点会磕磕绊绊但是能听得出来是有自己的思考的,对我在被拷打实习时从业务思考他提的问题的角度表示认可,闲聊里我说的一些观点也很认可。说他其实很清楚其实我们作为实习生,基本不可能自己实现一个模块,简历上都是有所包装的,他更看重应届的思考能力和学习能力,以及是否有学到东西。但也指出我有不少地方的知识并不算扎实,有些只是停留在表面。认为最好是后面做项目或者学习的时候更深入一点。不要只做到80%就够了,尽量做到100%,这样才会有更多的思考和收获(u1s1真的诚恳,一扫之前面试被鸽的坏印象)有几面?啥时候知道结果(技术面应该就一面,之后是hr面,啥时候知道结果他也不知道)面试体验整体还是不错的,也可能是因为我被鸽了40分钟所以才表现的比较诚恳。但vivo暑期招的人太少了,估计不会要我,还是提前认为凉了吧#我的实习求职记录# #如何判断面试是否凉了#
点赞 评论 收藏
分享
评论
5
4
分享

创作者周榜

更多
牛客网
牛客企业服务