继续刷LeetCode 热题 HOT 100 的题目,并且在博客更新我的solutions。在csdn博客中我会尽量用文字解释清楚,相关Java代码大家可以前往我的个人博客jinhuaiyu.com中查看。
今天的题目是涉及到两个点,一个是探索题目背后的数学关系,另一个是巧用排序。
题目:三数之和
给你一个包含 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
solution: 双指针+排序
在解题前,我们要剖析题目中的难点和可利用的数学关系。难点在于降低复杂度和去重。三个数我们总不能用O(n^3)的暴力方法求解吧,而且暴力求解碰到重复情况要不就得去比较,要不就得考虑利用hash,复杂程度飙升。而如果我们限定三元组必须是有序的,即first<=second<=third(有序无序都是同一个解),那么就能得到一些数学关系,比如:当first固定时,second变大,third只能变小;third再小不能小于second。那么就可以跳过一些不符合的情况不必讨论。当然,在这个过程中还有一些其他情况可以让我跳过很多讨论。于是乎,排序的必要性就显而易见了,我们需要先将原数组排序,并且在遍历讨论时,一直保持first<=second<=third。同时,由于排序了,重复的数字会排到一起,可以更容易地判断三元组重复情况。
将原数组进行升序排序后,我们进入具体分析。为了不走回头路,我们可以采取先固定first为新数组第一个数,这样问题就变成了,在first的右边找两个数和为partSum=-nums[first]。对first从左往右遍历,复杂度为O(n),而second和third只能在first的左侧找。
如果这一轮的first位置的值等于上一轮的first,那么这一轮找到的所有的三元组一定包含在上一轮找到的三元组中(即first值没变,但找second和third的数中少了一个数)。所以在每轮刚开始遍历first的时候,判断,是否和上一轮的first值相同,相同就不必继续讨论second和third,这样就跳过了很多情况。
如果新一轮的first值是不一样的,那么开始用双指针法寻找second和third。这里是将内循环的O(n^2)转化成O(n)的关键。second指针从first+1处开始从左往右移动,third指针从末尾往前移动。先遍历second(相当于每次移动second后固定second讨论third),在每次遍历second时,当nums[second]+ nums[third]>partSum时,逐渐减小third(third左移);当nums[second]+ nums[third]<=目标值时,third停止左移。因为此时再左移已经不可能满足条件了,这个时候必须把second变大(进入下一轮second循环,使second++)。新的一轮second固定中,third不必又从数组末尾开始找,因为second增大,third一定减少,直接从上一轮third停下的位置继续往左找即可。
注意,在每一轮second++开始,也要判断重复,如果second值和上一轮一样,那么符合的third一定还是一样的,后面的重复情况跳过讨论。
当third和second相遇时遍历second的循环就该被跳出了,移动first进入外层循环的下一轮。在每次固定了first、second,找到符合的third后将三元组记录下来。我们在first和second的固定时都考虑了去重从而减少重复情况讨论,但third是不用去重的,因为找到第一个符合的third的时候,就该去改变second或者first了,不会讨论同一组first和second下的下一个符合的third。
以上是官方和大多数人差不多的思想,但我发现还有一些情况可以被跳过讨论,从而进一步节省程序运行时间。
情况一:整个讨论过程中一直保持nums[first]<=nums[second]<=nums[third],如果其中最小的都大于0,那三者的和必不可能为0,可以直接结束讨论,这个break可以加在外层循环一开始对first进行检查。
情况二:因为nums[second] <= nums[third],如果2nums[second]大于partSum,那么后续不论second右移还是third左移,nums[second]+ nums[third]都不可能为partSum;2nums[third] < partSum时同理,这两种情况也可以直接退出second的循环。
加上上面的两种break后,时间和空间都比原来缩减了一点。
Finally,带有详细注释的代码放在放在我的个人博客http://jinhuaiyu.com/leetcode-3sum/
相关文章
- 10-18LeetCode 热题 HOT 100 第40天:“对称二叉树”
- 10-18leetcode hot 100-15. 三数之和
- 10-18LeetCode 热题 HOT 100 1. 两数之和
- 10-18[LeetCode 热题 Hot 100] 1.两数之和
- 10-18LeetCode 热题 HOT 100 第14天:“合并K个升序链表”
- 10-18LeetCode热题100 1.两数之和
- 10-18LeetCode 热题 HOT 100 第26天:“合并区间”
- 10-18LeetCode 热题 HOT 100 第2天:“两数相加”
- 10-18LeetCode 热题 HOT 100 第20天:“接雨水”
- 10-18LeetCode 热题 HOT 100 第23天:“字母异位词分组”