Three sum解题心得
题目重述
在一串整数序列里找到这样三个和为零的数,找出该序列中所有这样的组合,以小到大的顺序排列,答案中要求不存在相同的组
例子:
For example, given array S = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
解题过程
开始由于写过2Sum,就想过类比题目到3Sum中,只要遍历数组每个元素,设当前元素为 S[i] ,设置其相反数为target,相当于2Sum问题中的两个数和的结果,由此可以将问题化解为多个2Sum子问题,同时时间复杂度可以达到 O(n2) ,由于2Sum的最优复杂度为 O(n) 。
但是。。。
当我匆匆打完过了测试样例,交上去后,讨厌的Wrong Answer出来了
最后发现这里需要将结果进行排序和消重,,,,于是就重新改进了自己的算法:
版本一思路
算法思想
- 对S使用sort排序(时间复杂度 O(n∗logn)
- 遍历S[i],在i+1到n(n为S长度)之间做2Sum问题,target=-S[i]
- 2Sum部分(为了顺序要求,与原先的2Sum有所不同,两次遍历效率降低)
- 先用hash存取i+1到n的元素,按照{S[i],i}的形式存入hash
- 再次由j从i+1到n遍历,通过之前的hash表快速查找S[j]互补的另一个数com,使S[j]+com=target
- 每找到一组数则将结果插入输出数组
过程中可能遇到的bug (主要都是在消重排序上遇到的问题)
- i,j遍历时需要跳过连续的元素,以防重复查询,所以这里的循环都用的是while,其中i是外层循环,j是内层循环做2Sum查找
- 跳过重复元素的代码
//外层循环 while(i+1<n&&sortedNums[i]==sortedNums[i+1]) i++; i++;
//内层循环 j++; while(j<n&&j+1<n &&sortedNums[j-1]==sortedNums[j] &&sortedNums[j+1]==sortedNums[j]) j++;
- 注意:在内层循环里的时候可以当前S[j]获得与S[j]互补的元素com,如果在S[j]之前则说明这个组合已经在结果中了,说明已经过了这个小序列的关于target的中心(因为序列有序,则x+y=target的一对组合是从序列两端向中间靠近的,有一对最近的x,y就当作target的临界值),后面遇到的组合都是不需要再去搜索了,可以跳出当前j的循环,结束2Sum
- !!!push_back后还要把对应找到的S[j]互补的数com从hash表里删除,不然还是可能在关于targetd的中心处出现重复组合问题
- hint:外层循环里加入判断,判断当前位置nums[i]如果大于0,则后面的搜索是找不到新的目标序列的(但是不知道为什么加入这个后反而变慢了)
时间复杂度
总而言之,其中hash的索引复杂度为O(1),索引两次循环遍历,就是 O(n2) 的时间复杂度
丑陋的代码
这里贴个代码
我的代码链接:https://github.com/zhanzongyuan/leetcode/blob/master/015_3Sum.cpp
最后,算法accpted!但是运行时间特别慢,只击败1%的cpp代码
版本二思路
经过我的一波研究,发现其实这里有个性质我忽略了
有序序列两端互补:就是排序后的在i+1到n的序列里的特性,即x+y=target的互补特性没有用到
算法思想
- 同样是类似与一中的思想,将问题转换为2Sum,但是要用到有序序列两端互补的特性
- 当j在i+1到n的第二层循环里使用两个下标,分别从两端向中间找互补和为target的一对元素
- 找到后就插入到结果里,再同时向中间移动下标,直到跳过多个重复元素,碰到新的元素
- 重复2-3直到下标相遇,当i结束则得到所有的满足条件的组,同时符合大学顺序
- 注意:这里需要对i进行一中类似的重复元素跳过的操作
版本二代码
我的代码链接:https://github.com/zongyuanZhan/leetcode/blob/master/015_3Sum.cpp
再一次accept,速度提高很多!超过75%的代码。