首页 > 代码库 > [LeetCode] K sum(2Sum、3Sum、4Sum)

[LeetCode] K sum(2Sum、3Sum、4Sum)

1 2Sum

Given an array of integers, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution.

Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2

问题描述:给定一个整数数列,从其中找两个数字,使得他们的和等于一个给定的值。函数twoSum应该返回这两个数字的位置,而且位置索引以1开始。

这是一个面试的经典题目,题目本身并不难,但是要得出高效的算法还是需要动一番脑筋的。编程之美的2.12(快速寻找满足条件的两个数)中也提到了这个题目。

方法1:最简单的办法是用两个for循环,如果num[i]+num[j]==target,就得到了一个解。时间复杂度是O(N^2)。

方法2:编程之美上的最优解法。先对数列进行一下预处理,也就是排序,然后用两个变量,一个从前往后遍历,一个从后往前遍历。时间复杂度是O(NlogN)+O(N)=O(NlogN)。

int j = 0, k = n - 1;
while(j < k) {
    if(arr[j] + arr[k] == target) {
        res.push_back(arr[j]);
        res.push_back(arr[k]);
        ++j;
        --j;
    }
    else if(arr[j] + arr[k] < target) {
        ++j;
    }
    else {
        --k;
    }
}
上面的代码参考编程之美的,算是一个范式吧。

但是,本题有个不一样的地方是返回的是两个数字的索引,而不是两个值,因此,如果先排序的话,就必须记住元素在原数列中的索引。

bool compare(pair<int, int> lh, pair<int, int> rh)
{
	return lh.first < rh.first;
}

class Solution {
public:
	vector<int> twoSum(vector<int> &numbers, int target)
	{
		vector<pair<int, int> > num;
		int index = 1;
		for(vector<int>::iterator iter = numbers.begin();
			                      iter != numbers.end(); ++iter, ++index) {
			num.push_back(make_pair(*iter, index));
		}
		sort(num.begin(), num.end(), compare);
		int i = 0, j = num.size() - 1;
		vector<int> res;
		for(; i < j;) {
			if(num[i].first + num[j].first == target) {
				res.push_back(num[i].second);
				res.push_back(num[j].second);
				++i;
			}
			else if(num[i].first + num[j].first < target) {
				++i;
			}
			else {
				--j;
			}
		}
		return res;
	}
};


2 3Sum

Given an array S of n integers, are there elements abc in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

  • Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c)
  • The solution set must not contain duplicate triplets.

    For example, given array S = {-1 0 1 2 -1 -4},

    A solution set is:
    (-1, 0, 1)
    (-1, -1, 2)
问题描述:给定一个整数数列,从中找到三个元素,使得他们的和为0,找到所有的三元组。

注意:结果的三元组中的元素以非降序排列,结果不包含重复的三元组。

这道题跟上面的2Sum类似,就是个数比它多一个,本题也是编程之美2.12的扩展问题。

最简单的想法就是看能不能将问题转换为2Sum。

首先,还是要排序,比如,-4,-1,-1,0,1,2。然后,我们取-4,再从剩下的-1,-1,0,1,2中找和为0-(-4)=4的两个元素,没有找到。再取-1,从剩下的元素中找和为0-(-1)=1的两个元素,问题是,剩下的元素应该包含第一个-1吗?也就是说,考虑剩下的元素时,应该考虑之前的元素吗?之前将第一个-1作为元组的第一个元素时已经考虑了剩下的所有元素,当然也考虑了第二个-1,因此,当取第二个-1作为元组的第一个元素时,只需要考虑它之后的元素。其实,对于2Sum也有类似的考虑,也就是结果一般也要求不重复。因此,有下列算法:

class Solution {
public:
	vector<vector<int> > threeSum(vector<int> &num) {
		vector<vector<int> > vecresult;
		if(num.size() < 3) {
			return vecresult;
		}

		vector<int> triple(3, 0);
		sort(num.begin(), num.end());
		int count = num.size() - 2;
		int prev = num[0];
		unordered_set<int> s;
		for(int i = 0; i < count; ++i) {
			if(i && num[i] == prev) {
				continue;
			}
			s.clear();

			triple[0] = num[i];
			int j = i + 1;
			int k = num.size() - 1;
			while(j < k) {
				int sum = num[j] + num[k];
				if(sum + triple[0] == 0 && s.find(num[j]) == s.end()) {
					s.insert(num[j]);
					triple[1] = num[j];
					triple[2] = num[k];
					vecresult.push_back(triple);
					++j;
					--k;
				}
				else if(sum + triple[0] < 0) {
					++j;
				}
				else {
					--k;
				}
			}
			prev = num[i];
		}

		return vecresult;
	}
};

上面对于求两个和三个分别采用了不同的去重方式:在求两个的和为某个值时,采用了unordered_set;求三个时用了一个变量prev,表示上次的第一个元素,当上次的第一个元素和这次一样时,就遍历下一个。


3 K Sum

4Sum也可以采用上面的方式,由于4Sum可以转换为3Sum,3Sum可以转换为2Sum,因此可以写个递归来实现KSum。


参考资料:

http://tech-wonderland.net/blog/summary-of-ksum-problems.html