K Sum问题:
给定一个数组nums和一个实数target,在数组nums中给出所有的K个数,使K个数之和为target。
1. LeetCode 1:2 Sum
解法1:
暴力求解:先固定一个数,然后遍历数组,找到另一个数,使两数之和为target。
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> idx;
size_t n = nums.size();
for (size_t i = 0; i < n; i++) {
for (size_t j = i + 1; j < n; j++) {
if (nums[i] + nums[j] == target) {
idx.push_back(i);
idx.push_back(j);
return idx;
}
}
}
return idx;
}
解法二:
双指针:先对原数组中的数进行从小到大排序(使用algorithm头文件中的sort函数,时间为O(nlgn))。然后定义两个指针分别指向数组的头部和尾部。接着两个指针分别移动,遍历整个数组:如果两个指针指向的元素之和sum小于target,则移动头指针;若大于target,则移动尾指针;若等于target,则找到了满足要求的解。需要注意的是,leetcode的原题中要求的是返回元素的下标,故找到相应的元素后,还需要在元数组中找到对应的下标。
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> num = nums;
sort(num.begin(), num.end());
vector<int> idx;
int i = 0;
int j = num.size() - 1;
while (i < j) {
int sum = num[i] + num[j];
if (sum == target) {
int n = nums.size();
for (int k = 0; k < n; k++) { // 在原数组中寻找解的下标
if (nums[k] == num[i]) {
idx.push_back(k);
}
else if (nums[k] == num[j]) {
idx.push_back(k);
}
if (idx.size() == 2) {
break;
}
}
break; // 只有一对组合满足要求,故找到一组解后就可结束遍历
}
else if(sum < target) {
i++;
}
else {
j--;
}
}
return idx;
}
解法三:
哈希表:使用map实现哈希表。遍历数组,在map中查找每一个元素对应的解:若解不在map中,说明该元素无解或者还没有找到解,则将该元素加入到map中。故一次遍历就一定能找到相应的解。
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
unordered_map<int, int> hashs;
int n = nums.size();
for (int i = 0; i < n; i++) {
if (hashs.end() != hashs.find(target - nums[i])) {
res.push_back(hashs[target - nums[i]]);
res.push_back(i);
return res;
}
hashs[nums[i]] = i;
}
return res;
}
2. LeetCode15:3 Sum
本题要求返回所有的元素,而不是元素的下标。依然可以使用暴力求解,但显然效率太低。我们可以先固定一个数,然后将3 Sum问题转化为2 Sum问题。需要注意的是,本题需要排除重复解。
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans;
size_t n = nums.size();
if (n < 3) return ans;
sort(nums.begin(), nums.end()); // 将数组从小到大排序
for (size_t i = 0; i < n - 2; i++) {
/* 在nums[i+1, n-1]查找和为-nums[i]的两个数 */
int begin = i + 1;
int end = n - 1;
while (begin < end) {
int sum = nums[begin] + nums[end];
if (sum == -nums[i]) {
vector<int> res = { nums[i], nums[begin], nums[end] }; // 一组解
ans.push_back(res);
begin++;
end--;
/* 排除重复解 */
while (begin < end && nums[begin] == nums[begin - 1]) {
begin++;
}
while (begin < end && nums[end] == nums[end + 1]) {
end--;
}
}
else if (sum < -nums[i]) {
begin++;
}
else {
end--;
}
}
/* 排除重复解 */
while (i < n - 2 && nums[i] == nums[i + 1]) {
i++;
}
}
return ans;
}
3. LeetCode16:3 Sum Closest
本题是3 Sum问题的变形,即若nums中没有满足三数之和为target的解,就返回三数之和最接近target的解。本题思路与3 Sum问题相同,只不过需要计算当前的三数之和sum与target之间的距离dis,并更新最小距离mdis。
int threeSumClosest(vector<int>& nums, int target) {
if (nums.size() < 3) return NULL;
int closest;
int mdis = INT_MAX; // 最小距离
int n = nums.size();
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 2; i++) {
int begin = i + 1;
int end = n - 1;
while (begin < end) {
int sum = nums[i] + nums[begin] + nums[end];
if (sum == target) {
return target;
}
else if(sum < target) {
int dis = target - sum; // 当前和与目标值的距离
if (dis < mdis) { // 更新最小距离
mdis = dis;
closest = sum; // 记录最接近目标值的和
}
begin++;
}
else {
int dis = sum - target;
if (dis < mdis) {
mdis = dis;
closest = sum;
}
end--;
}
}
}
return closest;
}
4. LeetCode18:4 Sum
4 Sum问题的思路与3 Sum问题的思路相同,只不过3 Sum问题先固定一个数,4 Sum问题需要先固定两个数,体现在程序中就是多套一层for循环。同样需要注意的是重复解的问题:对于K个数相加,k - 1个加数确定,和也确定,则最后一个加数就一定是确定的,因此需要对k - 1个加数进行判重。
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int len = nums.size();
for (int i = 0; i < len - 3; i++) {
for (int j = i + 1; j < len - 2; j++) {
int begin = j + 1, end = len - 1;
while (begin < end) {
int sum = nums[i] + nums[j] + nums[begin] + nums[end];
if (sum == target) {
vector<int> tmp = { nums[i], nums[j], nums[begin], nums[end] };
res.push_back(tmp);
begin++;
end--;
/* 排除重复解 */
while (begin < end && nums[begin] == nums[begin -1]) {
begin++;
}
while (begin < end && nums[end] == nums[end + 1]) {
end--;
}
}
else if(sum > target) {
end--;
}
else {
begin++;
}
}
/* 排除重复解 */
while (j < len -2 && nums[j] == nums[j + 1]) {
j++;
}
}
/* 排除重复解 */
while (i < len - 3 && nums[i] == nums[i + 1]) {
i++;
}
}
return res;
}
5. LeetCode454:4 Sum II
本题严格来说已经不是K Sum问题,因为本题要求在四个等长的额数组中找到target为0的解的个数。本题现在遍历两个数组,将每一个可能的和记录在map中,如果出现相同的数,则在map中将对应的value再加1。再遍历剩余两个数组,计算任意两个数的和,并判断能否与map中的数组成target为0的解。用变量count记录解的个数,若存在满足要求的解,则count加上map中的变量对应的值。
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
int len = A.size();
int count = 0;
unordered_map<int, int> hash;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
hash[A[i] + B[j]]++;
}
}
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
if (hash.find(-C[i] - D[j]) != hash.end()) {
count += hash[-C[i] - D[j]];
}
}
}
return count;
}