1. 两数之和
题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
题目要点:
1:数组无序。
2:因为是返回数的下标,排序再用双指针需要额外维护数的原索引。
解法1:穷遍历
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
if (len < 2)
return {};
for (int i = 0; i < len-1; ++i)
for (int j = i+1; j < len; ++j) {
if (nums[i] + nums[j] == target) {
vector<int> res(2);
res[0] = i;
res[1] = j;
return res;
}
}
return {};
}
};
//go
func abs(a, b int) int {
if a > b {
return a-b
}
return b-a
}
func twoSum(nums []int, target int) []int {
lenth := len(nums)
if lenth < 2 {
return []int{}
} else if lenth == 2 {
return []int{0, 1}
}
for i := 0; i < lenth-1; i++ {
for j := i+1; j < lenth; j++ {
if nums[i] + nums[j] == target {
return []int{i, j}
}
}
}
return []int{}
}
解法2:用迭代器穷遍历
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
if (len < 2)
return {};
int another;
for (auto pos = nums.begin(); pos != nums.end(); ++pos) {
another = target - *pos;
auto otherPos = find(pos+1, nums.end(), another);
if (otherPos != nums.end()) {
vector<int> res(2);
res[0] = pos - nums.begin();
res[1] = otherPos - nums.begin();
return res;
}
}
return {};
}
};
解法3:用hash表记录已经判断过的数
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int len = nums.size();
if (len < 2)
return {};
int another;
unordered_map<int,int> myHash;
for (int i = 0; i < len; ++i) {
another = target - nums[i];
if (myHash.count(another)) { //myHash.find(another) != myHash.end()
vector<int> res(2);
res[0] = myHash[another];
res[1] = i;
return res;
}
myHash[nums[i]] = i; //当前值为key,index为key的val
}
return {};
}
};
//go
func twoSum(nums []int, target int) []int {
castMap := make(map[int]int, len(nums))
for i, v := range nums {
if j, ok := castMap[target - v]; ok {
return []int{j, i}
}
castMap[v] = i
}
return []int{}
}
15. 三数之和
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目要点:
1:数组无序。
2:三数之和为target,target = 0
3:结果不含重复三元组。
4:如果不排序,需额外处理重复三元组,时间空间会惨不忍睹
解法1:先固定一个数,再双指针
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int len = nums.size();
if (len < 3)
return {};
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int first = 0; first < len-2; first++) {
if (nums[first] > 0) //第一个数最小数不能大于0
break;
if (first>0 && nums[first-1] == nums[first]) //第一个数去重
continue;
for (int left = first+1, right = len-1; left < right; left++) {
if (first+1 < left && nums[left-1] == nums[left]) //第二个数去重
continue;
while (left < right && nums[first]+nums[left]+nums[right] > 0)//第三个数太大
--right;
if (left >= right)
break;
if (nums[first]+nums[left]+nums[right] == 0) {
vector<int> tmp(3);
tmp[0] = nums[first];
tmp[1] = nums[left];
tmp[2] = nums[right];
res.push_back(tmp);
}
}
}
return res;
}
};
另一种编写方法如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
int len = nums.size();
if (len < 3)
return res;
sort(nums.begin(), nums.end()); //排序
for (int i = 0; i < len-2; ++i) {
int left = i+1, right = len-1;
if (i && nums[i-1] == nums[i]) //第一个数去重
continue;
if (nums[i] > 0 || nums[right] < 0) //条件短路
break;
while (left < right) { //二分法
if (i+1 < left && nums[left-1] == nums[left]) { //第二个数去重
++left;
continue;
}
int sum = nums[i]+nums[left]+nums[right];
if (sum == 0) {
res.push_back({nums[i], nums[left], nums[right]}); //符合同时移动两指针
++left;
--right;
}
else if (sum < 0)
++left;
else
--right;
}
}
return res;
}
};
此解法改成先固定一个数,再求两数之和的格式,程序参考如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int len = nums.size();
if (len < 3)
return {};
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int first = 0; first < len-2; first++) {
if (nums[first] > 0) //第一个数最小数不能大于0
break;
if (first>0 && nums[first-1] == nums[first]) //第一个数去重
continue;
int targetTwo = -nums[first]; //剩余两数之和
for (int left = first+1, right = len-1; left < right; left++) {
if (nums[left] > targetTwo) //此数不能大于两数之和
break;
if (first+1 < left && nums[left-1] == nums[left]) //第二个数去重
continue;
while (left < right && targetTwo < nums[left]+nums[right]) //第三个数太大
--right;
if (left >= right)
break;
if (targetTwo == nums[left]+nums[right]) {
vector<int> tmp(3);
tmp[0] = nums[first];
tmp[1] = nums[left];
tmp[2] = nums[right];
res.push_back(tmp);
}
}
}
return res;
}
};
综上,能够总结一个模式:
1:先固定前n-2个数。
1)如果其值大小明显不符合要求,排除此数。
2)若此数是已处理过的重复数,再排除。
2:然后再用双指针求剩余两数。
1)2)与上述一致,然后比较现有和和target的大小,若和小,移动左指针,若大移动右指针,否则计入结果。
需要指出的一点是,只有target非负时需要1中1),若target正负未知,1中1)是不该有的。因为当target为负时,第一个最小数minest和target的大小是无法比较的,例如minest(-8)< target(-6),minest(-6) = target (-6),minest(-2)> target (-6)都可以成立,但此时minest可以是合法的,例如-8会遇上正数,-6会遇上0,-2会遇上负数。这一点在如下四数之和可以明显地表现出来。
18. 四数之和
题目:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
题目要点:
1:和三数之和不同的是,target未知。
解法:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int len = nums.size();
if (len < 4)
return {};
sort(nums.begin(), nums.end());
vector<vector<int>> res;
for (int first = 0; first < len-3; first++) {
//if (nums[first] > target)
// break;
if (first>0 && nums[first-1] == nums[first]) //第一个数去重
continue;
int targetThree = target - nums[first]; //剩余三数之和
for (int second = first+1; second < len-2; second++) {
//if (nums[second] > targetThree)
// break;
if (second > first+1 && nums[second-1] == nums[second]) //第二个数去重
continue;
int targetTwo = targetThree - nums[second]; //剩余两数之和
for (int third = second+1, fouth = len-1; third < fouth; third++) {
//if (nums[third] > targetTwo)
// break;
if (third > second+1 && nums[third-1] == nums[third]) //第三个数去重
continue;
while (third < fouth && targetTwo < nums[third] + nums[fouth]) //第四个数太大
--fouth;
if (third >= fouth)
break;
if (targetTwo == nums[third] + nums[fouth]) {
vector<int> tmp(4);
tmp[0] = nums[first];
tmp[1] = nums[second];
tmp[2] = nums[third];
tmp[3] = nums[fouth];
res.push_back(tmp);
}
}
}
}
return res;
}
};
按照三数之和的解法思路得到上述解法中,与之不同的是注释部分,因为target正负未知,应该注掉,这一点已在三数之和部分解释过,不理解的同学可以去掉//跑一边测试例,然后分析下某些测试例通不过的原因。
综上,解决此类n数之和的思路就是,排序数组,然后对于固定的数去重,就最后两个数利用和大小有效缩小区间。此外,排除明显不满足的情况,避免不必要的计算和比较。
16. 最接近的三数之和
题目:
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
解法1:穷遍历
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int len = nums.size();
if (len < 3)
return -1;
int minDiff = INT_MAX, diff;
int res = 0;
for (int first = 0; first < len-2; first++) {
for (int second = first+1; second < len-1; second++) {
for (int third = second+1; third < len; third++) {
int sum = nums[first] + nums[second] + nums[third];
diff = abs(target - sum);
if (diff < minDiff) {
minDiff = diff;
res = sum;
}
}
}
}
return res;
}
};
解法2:类似三数之和
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
int len = nums.size();
if (len < 3)
return -1;
sort(nums.begin(), nums.end());
int minDiff = INT_MAX, diff;
int res = 0;
for (int first = 0; first < len-2; first++) {
if (first>0 && nums[first-1] == nums[first]) //第一个数去重
continue;
for (int second = first+1, third = len-1; second < third;) {
if (second>first+1 && nums[second-1] == nums[second]) { //第二个数去重
second++;
continue;
}
int sum = nums[first] + nums[second] + nums[third];
diff = abs(target - sum);
if (diff < minDiff) {
minDiff = diff;
res = sum;
if (0 == diff)
return res;
}
if (sum < target) //利用sum的大小左右缩小范围
second++;
else if (sum > target)
third--;
}
}
return res;
}
};