每周算法:三数之和
Description
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] ]
Solution
Approach 1
这道题有点难度,第一时间没有什么思路。我想到的主要问题是如何保证返回结果中不包含重复的一组数。如果使用暴力解法,将所有满足条件的组合取到后进行重复性清洗,应该是一种可行的解法,只是这样做时间复杂度有点高。
下面的代码就是暴力解法的实现。不出意外,在leetcode中结果为超时。这种算法的时间复杂度为 O(n^3 * nlogn) + O(n^2)
。
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
for (size_t i=0; i<nums.size(); ++i) {
int n1 = nums[i];
for (size_t j=i+1; j<nums.size(); ++j) {
int n2 = nums[j];
for (size_t k=j+1; k<nums.size(); ++k) {
int n3 = nums[k];
if (n1 + n2 + n3 == 0) {
vector<int> triplet{n1, n2, n3};
sort(triplet.begin(), triplet.end());
ans.push_back(triplet);
}
}
}
}
// 将结果中的重复组合删掉
for (vector<vector<int>>::iterator it1 = ans.begin();
it1 != ans.end(); ++it1) {
for (vector<vector<int>>::iterator it2=it1+1; it2!=ans.end();) {
if (*it1 == *it2) {
it2 = ans.erase(it2);
}
else {
++it2;
}
}
}
return ans;
}
从上面这个解法中,我能够得到的 教训 经验是对 vector
进行具有删除操作的循环时,使用迭代器完成更不容易出错。我之前的去重逻辑是这样的。
for (int i=0; i<ans.size(); ++i) {
for (int j=i+1; j<ans.size(); ++j) {
if (ans[i] == ans[j]) {
ans.erase(ans.begin() + j);
}
}
}
很显然,上面的代码在一些情况下并不能彻底删去所有重复的元素,下面的代码才是真正正确的实现。
for (int i=0; i<ans.size(); ++i) {
for (int j=i+1; j<ans.size();) {
if (ans[i] == ans[j]) {
ans.erase(ans.begin() + j);
}
else {
++j;
}
}
}
Approach 2
我认为这到题的主要难点啊主要是在去除重复的问题上。下面的解法来自 leetcode ,在处理重复性问题时,在匹配答案时将重复的数字跳过,这样就不用在后续的结果中进行剔除重复元素的步骤了。
vector<vector<int> > threeSum(vector<int> &num) {
std::sort(num.begin(), num.end());
vector<vector<int> > res;
for (int i = 0; i < num.size(); i++) {
int target = -num[i];
int front = i + 1;
int back = num.size() - 1;
while (front < back) {
int sum = num[front] + num[back];
// Finding answer which start from number num[i]
if (sum < target)
front++;
else if (sum > target)
back--;
else {
vector<int> triplet = {num[i], num[front], num[back]};
res.push_back(triplet);
// Processing duplicates of Number 2
// Rolling the front pointer to the next different number forwards
while (front < back && num[front] == triplet[1])
++front;
}
}
// Processing duplicates of Number 1
while (i + 1 < num.size() && num[i + 1] == num[i])
++i;
}
return res;
}
上面的代码中,只需要跳过 front
所对应的数字就好,因为 back
对应的数字会自然地随 front
变化而变化。