15. 3Sum

1.题目描述:

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]
]

来自 <https://leetcode.com/problems/3sum/> 

2.问题分析:
经典的3sum问题,在比较多的面经中都会讲到,可见这道题和变种题在面试中出现的概率还是相当大的。
从数组中找出三个数,使得和为0;暴力的方法就是一个一个试验,算法复杂度O(n3),当输入是超长数组时,严重超时。同时有一个需要处理的是,不能出现重复的答案,暴力的方法不进行这种处理,最后的结果中会出现比较多的重复项,必须对结果进行二次处理(处理的复杂度O(n2)),才能返回。

3.c++代码:

//Time Limit Exceeded
vector<vector<int>> threeSum(vector<int>& nums)
{
	vector<vector<int>> rr;
	if (nums.empty())
		return rr;
	vector<int>v(nums);
	sort(v.begin(), v.end());
	int n = v.size();
	for (int mid = 1; mid < n - 1; mid++)
	{
		for (int left = 0; left < mid; left++)
		{
			for (int right = mid + 1; right < n; right++)
			{
				if (v[left] + v[right] + v[mid] == 0)
				{
					vector<int>tmp;
					tmp.push_back(v[left]);
					tmp.push_back(v[mid]);
					tmp.push_back(v[right]);
					rr.push_back(tmp);
				}
			}
		}
	}
	if (rr.size() > 1)
	{
		vector<vector<int>> rrr;
		rrr.push_back(rr[0]);
		for (vector<vector<int>>::iterator i = rr.begin(); i != rr.end(); i++)
		{
			int cnt = 0;
			for (vector<vector<int>>::iterator j = rrr.begin(); j != rrr.end(); j++)
			{
				if (*i == *j)
					cnt++;
			}
			if (cnt == 0)
				rrr.push_back(*i);
		}
		return rrr;
	}
	else
	return rr;
}

关于下面这个方法的分析:

  • 先对数组进行排序O(NlogN)
  • 定义左右两个指针,中间数从左往右遍历,左指针在左区间活动,右指针在右区间活动
    根据求和结果移动指针,如果和<0,说明,左边的数偏小,更新向中间数方向更新左指针,如果和>0,说明,右边的数偏小,向中间数方向更新右指针,知道找到合适的答案;
  • 怎么去除重复答案的?
    首先,重复答案有两种,一种是左右指针的数重复,另一种是中间数重复;
    对于第一种的解决方法是,在找到合适答案时,移动左右指针,直到左右数不重复;
    第二种的解决方法是,保存上次的合适答案的指针位置,如果当前中间数和上一个中间数一样,则从上次指针的位置的开始查找,这样就跳过了重复的中间数对象的重复左右指针数了,这个办法通过缩短左右区间,跳过出现重复左右数的区域。
//beats 43.52%
vector<vector<int>> threeSum2(vector<int>& nums)
{
	vector<vector<int>>res;
	if (nums.empty())
		return res;
	sort(nums.begin(), nums.end());
	int n = nums.size();
	int tmp_l = 0;int tmp_r = n - 1;
	int left = 0;int right = n - 1;
	for(unsigned int mid=1;mid < n - 1;mid++)
	{
		if (nums[mid] == nums[mid - 1])
			{left = tmp_l; right = tmp_r;}
		else
			{ left = 0;right = n - 1;}
		while (left<mid&&right>mid)
		{
			int s = nums[mid] + nums[left] + nums[right];
			if (s < 0)left++;
			else if (s > 0)right--;
			else
			{
				res.push_back(vector<int>{ nums[left], nums[mid], nums[right]});
				while (nums[left] == nums[left + 1])left++;
				while (nums[right] == nums[right - 1])right--;
				left++;
				right--;
				tmp_l = left;
				tmp_r = right;
			}
		}
	}
	return res;
}

4.图表分析:
输入:[-1,0,1,2,-1,-4,2,-1]
排序后:[-4,-1,-1,0,1,2,2,-1]

01234567
-4-1-1-10122

1.mid=1

01234567sum
-4-1-1-10122
leftmidright-3
tmp_lefttmp_right

sum=-4±1+2=-3<0,更新left,left==mid,停止

2.mid=2

01234567sum
-4-1-1-10122sum
leftmidright-3
leftmidright0
leftmidright0
tmp_lefttmp_right

找到合适的答案:[-1,-1,2]
右指针区域的两个2都是满足条件的,更新right到最左边的2
tmp_left/tmp_right更新到下一个位置
3.mid =3

01234567sum
-4-1-1-10122
leftmidright1
leftmidright0
tmp_left/tmp_right

由于nums[3]=-1上次出现过了,本轮产找的区间不再是从数组的两个边界开始,而是从tmp_left和tmp_right开始。
4.mid=4

01234567sum
-4-1-1-10122
leftmidright-2
leftmidright1
leftmidright-1
leftmidright0
tmp_lefttmp_right

找到合适的答案:[-1,0,1]

5.mid=5

01234567sum
-4-1-1-10122
leftmidright-1
leftmidright2
leftmidright2

5.mid=6

01234567sum
-4-1-1-10122
leftmidright0
tmp_lefttmp_right

找到合适的答案:[-4,2,2]

参考:
https://leetcode.com/problems/3sum/discuss/7402/Share-my-AC-C%2B%2B-solution-around-50ms-O(N*N)-with-explanation-and-comments

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值