leetcode 15 三数之和
1. 题意
在数组中找到 和为指定值的三元组。
2. 题解
- 排序 + 双指针
这个题目最关键的有两点:
一是利用排序和与前一个元素比较来去重;
二是通过数量关系写出双指针的解法。
2.1 暴力
三重循环枚举三元组,时间复杂度O(n3)O(n^3)O(n3)
当然会有重复的,我们排序后,那些相同的三元组肯定会排在一块的。
因此可以通过与前一个元素比较来去重。
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort( nums.begin(), nums.end());vector<vector<int>> ans;int n = nums.size();for (int i = 0;i < n - 2; ++i) {for (int j = i + 1; j < n - 1; ++j) {for (int k = j + 1; k < n; ++k) {if ( nums[i] + nums[k] + nums[j] == 0) {ans.push_back({ nums[i], nums[j], nums[k]});}}}}auto cmp = [](const vector<int> &a, const vector<int> &b) {if ( a[0] != b[0] )return a[0] < b[0];if ( a[1] != b[1] )return a[1] < b[1];return a[2] < b[2];};sort(ans.begin(), ans.end(), cmp );if (ans.size() > 1) {for (auto it = ans.begin() + 1; it != ans.end(); ) {if ( *it == *(it - 1)) {it = ans.erase(it);}else {++it;}}}return ans;}
};
实际上我们可以一开始就把数组给排序,从而避免重复三元组的枚举来达到去重的目的。
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort( nums.begin(), nums.end());vector<vector<int>> ans;int n = nums.size();for (int i = 0;i < n - 2;++i) {if ( i && nums[i] == nums[i-1])continue;for (int j = i + 1; j < n - 1; ++j) {if ( j > i + 1 && nums[j] == nums[j-1]) continue;for (int k = j + 1; k < n; ++k) {if ( k > j + 1 && nums[k] == nums[k - 1])continue;int v = nums[i] + nums[j] + nums[k];if (0 == v) {ans.push_back( {nums[i], nums[j], nums[k]});}}}}return ans;}
};
2.2 二分
我们可以在枚举三元组的基础上,只需要枚举二元组iji\ ji j;
并在区间[j+1,n)[j+1,n)[j+1,n)中查找−(nums[i]+nums[j])-(nums[i]+nums[j])−(nums[i]+nums[j])。
这样时间复杂度就降为了O(n2log2n)O(n^2\log_2 n)O(n2log2n)。
同样我们还是需要去重滴。
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort( nums.begin(), nums.end());vector<vector<int>> ans;int n = nums.size();for (int i = 0;i < n - 2;++i) {if ( i && nums[i] == nums[i-1])continue;for (int j = i + 1; j < n - 1; ++j) {if ( j > i + 1 && nums[j] == nums[j-1]) continue;int sv = -( nums[i] + nums[j] );auto it = lower_bound( nums.begin() + j + 1, nums.end(), sv );if ( it != nums.end() && *it == sv ) {ans.push_back({nums[i], nums[j], sv});}}}return ans;}
};
2.3 相向双指针
双指针的做法主要利用有序性,把j,kj,kj,k的枚举给关联起来了。
对于三重循环的枚举,j,kj,kj,k实际上相当于同向双指针的枚举。
我们根据a+b+c=a+b′+c′,b<b′a+b+c=a+b'+c',b<b'a+b+c=a+b′+c′,b<b′,必然可以得出c′<cc' < cc′<c。
由于我们的数组是排好序的,因此必然有
idx(b)<idx(b′)<idx(c′)<idx(c)idx(b) < idx(b') <idx(c')<idx(c)idx(b)<idx(b′)<idx(c′)<idx(c)
也就是在排序好的数组中,这四个数的位置依次是bb′c′cb\ b'\ c'\ cb b′ c′ c。
因此我们一开始的时候,可以把kkk位置放到最后进行枚举
j=i+1,k=n−1j=i+1,k=n-1 j=i+1,k=n−1
由于iii位置固定,实际子问题变成了在[j,k][j,k][j,k]中查找和为−nums[i]-nums[i]−nums[i]的
有序对。而这个问题其实就是两数之和 II。
更进一步的解释就不写了,可以参考两数之和II的题解。
时间复杂度O(n2)O(n^2)O(n2)
class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {sort( nums.begin(), nums.end());vector<vector<int>> ans;int n = nums.size();for (int i = 0;i < n - 2;++i) {if ( i && nums[i] == nums[i-1])continue;int j = i + 1;int k = n - 1;while ( j < k) {int lsum = -(nums[i] + nums[j]);if ( nums[k] > lsum)k--;else if ( lsum == nums[k] ) {ans.push_back( { nums[i], nums[j], nums[k]});j++;while (j < k && nums[j - 1] == nums[j])j++;} else {j++;}}}return ans;}
};
3. 参考
leetcode
0x3f