生命不止,刷题不息~~~~~~
前两天就一直在做15.三数之和,这个题在LeetCode和LeetCode中国上获赞很多,绝对的好题啊!不过,我喜欢这个题仅仅是因为它采用了快速排序的思想啦。
从捋清思路到代码实现,突破重重Bugs大关,终于提交成功,对于小白而言,实属不易,接下来就跟大家分享一下这个题的解题思路,并附上用C++,java,和python的代码实现,希望能给大家提供帮助!
1、题目:
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
2、思路分析:
刚刚我也提到了这个题采用快速排序的思想是很棒的解题思路,在这里,我想提供给大家也是这个解题思路:
大家看到这个题肯定瞬间会想到用三层循环呀,岂不是很简单?是的,但是这个思路想想就算了,就不劳您去实现了,在实际应用中,time complexity 为(n^3)的算法,除非迫不得已,我们还是不建议使用的。
在次之间如果大家已经刷过LeetCode上 11.盛最多水的容器 并且用快排的思想实现的,那3sum这个题对你来说就没有难度了。我们来分析一下这道题的特点,要我们找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,我们还是要先fix一个数,然后去找另外两个数,我们只要找到两个数且和为第一个fix数的相反数就行了,如何能更有效的定位呢?我们肯定不希望遍历所有两个数的组合吧,所以如果数组是有序的,那么我们就可以用双指针以线性时间复杂度来遍历所有满足题意的两个数组合。这个题了,那恭喜你,你对这个算法已经了解了一大半!
我们对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。这里我们可以先做个剪枝优化,就是当遍历到正数的时候就break,为啥呢,因为我们的数组现在是有序的了,如果第一个要fix的数就是正数了,那么后面的数字就都是正数,就永远不会出现和为0的情况了。然后我们还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为我们不想把相同的数字fix两次。对于遍历到的数,用0减去这个fix的数得到一个target,然后只需要再之后找到两个数之和等于target即可。我们用两个指针分别指向fix数字之后开始的数组首尾两个数,如果两个数和正好为target,则将这两个数和fix的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于target,则我们将左边那个指针start右移一位,使得指向的数字增大一些。同理,如果两数之和大于target,则我们将右边那个指针end左移一位,使得指向的数字减小一些。
感谢Grandyang提供了清晰的算法描述,感谢大神!
在此附上大神的博客链接:https://www.cnblogs.com/grandyang/p/4481576.html
3、代码实现:
C++实现
class Solution {
public:
vector> threeSum(vector& nums) {
vector> res;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); ++k) {
if (nums[k] > 0) break;
if (k > 0 && nums[k] == nums[k - 1]) continue;
int target = 0 - nums[k];
int i = k + 1, j = nums.size() - 1;
while (i < j) {
if (nums[i] + nums[j] == target) {
res.push_back({nums[k], nums[i], nums[j]});
while (i < j && nums[i] == nums[i + 1]) ++i;
while (i < j && nums[j] == nums[j - 1]) --j;
++i; --j;
} else if (nums[i] + nums[j] < target) ++i;
else --j;
}
}
return res;
}
};
运行时间:104ms 战胜 81.54%的提交用户
Java实现:
class Solution {
public List> threeSum(int[] num) {
Arrays.sort(num);
List> res = new LinkedList<>();
for (int i = 0; i < num.length-2; i++) {
if (i == 0 || (i > 0 && num[i] != num[i-1])) {
int lo = i+1, hi = num.length-1, sum = 0 - num[i];
while (lo < hi) {
if (num[lo] + num[hi] == sum) {
res.add(Arrays.asList(num[i], num[lo], num[hi]));
while (lo < hi && num[lo] == num[lo+1]) lo++;
while (lo < hi && num[hi] == num[hi-1]) hi--;
lo++; hi--;
}
else if (num[lo] + num[hi] < sum) lo++;
else hi--;
}
}
}
return res;
}
}
运行时间:84ms 战胜 56.92%的提交用户
Python实现:
def threeSum(nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
the capacibility of python original sort method is better than quick_sort
"""
# new_nums = self.quick_sort(nums, 0, len(nums)-1)
nums.sort()
start, end ,sum = 0,0,0
result = []
for i in range(len(nums)-2):
if i==0 or (i>0 and nums[i]!=nums[i-1]): # ensure result has no repeat list
sum = 0 - nums[i]
start = i+1
end = len(nums)-1
while start < end:
if nums[start] + nums[end] == sum:
each_list = [nums[i], nums[start], nums[end]]
result.append(each_list)
while start < end and nums[start]==nums[start+1]:
start +=1
while start < end and nums[end]==nums[end-1]:
end -=1
start +=1
end -=1
elif nums[start] + nums[end] < sum:
start +=1
elif nums[start] + nums[end] > sum:
end -=1
return result
运行时间:1052ms 战胜 38.11%的提交用户
4、通过比较分析,可以看出java的执行效率是最高的,这也是我为什么要执着于java的原因。
再次感谢给本题提供解题思路的博主们,狗子们一起加油~~~