15. 3Sum
题目描述
Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note:
The solution set must not contain duplicate triplets.
Example:
Given array nums = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路分析
暴力解决法是每个人都能想到的,三层for循环,时间复杂度是O(n^3),而且还要处理重复的问题,显然不是题目想要的解法。
那能不能降到O(n^2)?排序算法的时间复杂度为O(nlgn),小于O(n^2),那么我们不妨先对数组排个序。
排序之后,我们就可以对数组用两个指针分别从前后两端向中间扫描了,如果是 2Sum,我们找到两个指针之和为target就OK了,那 3Sum 类似,我们可以先固定一个数,然后找另外两个数之和为第一个数的相反数就可以了。
要我们找出三个数且和为0,那么除了三个数全是0的情况之外,肯定会有负数和正数,我们还是要先确定一个数,然后去找另外两个数,我们只要找到两个数且和为第一个确定数的相反数就行了。
我们对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。
对于遍历到的数,用0减去这个确定的数得到一个target,然后只需要再之后找到两个数之和等于target即可。我们用两个指针分别指向fix数字之后开始的数组首尾两个数,如果两个数和正好为target,则将这两个数和fix的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于target,则我们将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于target,则我们将右边那个指针j左移一位,使得指向的数字减小一些。
代码实现
C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> 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;
}
};
java
public class Solution {
List<List<Integer>> ret = new ArrayList<List<Integer>>();
public List<List<Integer>> threeSum(int[] num) {
if (num == null || num.length < 3) return ret;
Arrays.sort(num);
int len = num.length;
for (int i = 0; i < len-2; i++) {
if (i > 0 && num[i] == num[i-1]) continue;
find(num, i+1, len-1, num[i]); //寻找两个数与num[i]的和为0
}
return ret;
}
public void find(int[] num, int begin, int end, int target) {
int l = begin, r = end;
while (l < r) {
if (num[l] + num[r] + target == 0) {
List<Integer> ans = new ArrayList<Integer>();
ans.add(target);
ans.add(num[l]);
ans.add(num[r]);
ret.add(ans); //放入结果集中
while (l < r && num[l] == num[l+1]) l++;
while (l < r && num[r] == num[r-1]) r--;
l++;
r--;
} else if (num[l] + num[r] + target < 0) {
l++;
} else {
r--;
}
}
}
}
python
按照思路写的代码超时了,这里是别人的两种写法:
使用哈希+双指针
时间复杂度:O(n^2),假设hash是O(1)的话。
class Solution:
def twoSum(self, nums, target):
idxDict = dict()
idx_list = []
for idx, num in enumerate(nums):
if target - num in idxDict:
idx_list.append([idxDict[target - num], idx])
idxDict[num] = idx
return idx_list
def threeSum(self, num):
num.sort()
res = dict()
result = []
for i in range(len(num)-2): # 遍历至倒数第三个,后面两个指针
if (i == 0 or num[i] > num[i-1]) and num[i] <= 0: # 只检索不重复并且目标数(第一个数)小于等于0的情况
left = i + 1;
# right = len(num) - 1
result_idx = self.twoSum(num[left:], -num[i])
for each_idx in result_idx: # 数组后方切片后给twoSum
each_result = [num[i], num[each_idx[0]+(i+1)], num[each_idx[1]+(i+1)]]
if str(each_result) not in res:
res[str(each_result)] = each_result
for value in res.values():
result.append(value)
return result
双指针,但别人优化的更好
时间复杂度:O(N^2)+O(N) = O(N^2),但显然比上面一种解法复杂度更高
class Solution:
def threeSum(self, num):
num.sort()
res = []
for i in range(len(num)-2): # 遍历至倒数第三个,后面两个指针
if i == 0 or num[i] > num[i-1]:
left = i + 1
right = len(num) - 1
while left < right: # 值得注意的是,这里左右指针将这个数所有情况都遍历加入,所以遇到同样的数直接跳过
if num[left] + num[right] == -num[i]:
res.append([num[i], num[left], num[right]])
left += 1; right -= 1
while left < right and num[left] == num[left-1]: left +=1
while left < right and num[right] == num[right+1]: right -= 1
elif num[left] + num[right] < -num[i]:
while left < right:
left += 1
if num[left] > num[left-1]: break
else:
while left < right:
right -= 1
if num[right] < num[right+1]: break
return res