题目链接:四数相加
视频讲解:学透hash表,map使用有技巧!
为了减少时间复杂度,四个数组可以分两部分遍历。
方法
1、先定义一个unordered_map
2、在A、B、C、D四个数组中先遍历A、B数组,求A、B两个中元素的和a + b。用map的key存和a + b,value存两数之和出现的次数;
3、定义一个count,记录满足a + b + c + d =0的次数;
4、一起遍历C、D数组,求和c + d,从map中找到满足0 - (c + d)的key,读取key对应的value即是满足条件的个数,把它加到count中;
5、最后返回统计值count即可;
// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
class Solution {
public:
int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
unordered_map m;
for (auto n1 : nums1)
{
for (auto n2 : nums2)
{
m[n1 + n2]++; // 求和的值存入key中
}
}
int count = 0; // 统计a+b+c+d=0出现的次数
for (auto n3 : nums3)
{
for (auto n4 : nums4)
{
int t = 0 - (n3 + n4);
if (m.find(t) != m.end()) // 找到map中满足0-(c+d)的key所对应的value
{
count += m[t];
}
}
}
return count;
}
};
题目链接:赎金信
这题与242有效字母异位词很像,可以采用数组的方法。但用数组保存哪个字符串的内容需要确定好。
方法
1、先判断两个字符串的长度,如果被表示的更长,直接返回false;
2、用数组r[] 存储26个字符,先把magazine中的字符存入数组中,用下标表示,重复的字符下标所对应的值++;
3、遍历ransomNote,若在r[] 中找到ransomNote中的字符,该字符下标所对应的值--;
4、判断r[] 中每个字符数如果小于0,表示ransomNote中有字符而在magazine中没有,返回false,否则返回true;
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int r[26] = {0};
if (ransomNote.size() > magazine.size())
{
return false;
}
for (int i = 0; i < magazine.size(); ++i)
{
r[magazine[i] - 'a']++; // 记录字母在magazine中出现的次数
}
for (int i = 0; i < ransomNote.size(); ++i)
{
r[ransomNote[i] - 'a']--;
if (r[ransomNote[i]- 'a'] < 0)
{
return false;
}
}
return true;
}
};
题目链接:三数之和
视频讲解:梦破碎的地方!
此题不适合用哈希法,采用双指针法会更高效些,更容易去重。
方法
1、定义一个二元数组结果;
2、先将数组有小到大排序,然后用for循环i从0遍历排序后的数组。同时定义一个left表示数组下标为i+1的位置,right表示数组下标最后一个位置;
3、接下来移动left和right。用a,b,c表示数组下标为i,left,right所对应的值,如果a+b+c>0则表示三数之和大了,需要减小。此时排序的作用就体现出来了,只需将right左做移动,三数之和就会变小。同理,若a+b+c<0则表示三数之和小了,就让left向右移动。当left与right相遇结束循环。
去重
主要是对a,b,c去重。首先a是遍历数组得到的,先对a去重。如果a重复了,应该直接跳过去,但有两种办法判断两个相邻的元素是否相等:nums[i] = nums[i + 1] 和 nums[i] = nums[i -1],那么这两个是否能达到同样的效果,我们用哪个好呢?
如果用第一种判断
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那么就会把三元组中出现重复元素的情况忽略掉。如{-1, -1, 2},当遍历到第一个-1时,第二个也是-1,那么这组数组就会pass,进行下一轮循环。
本题需要的是不重复的三元组,不是三元组内不能有重复的元素
所以应该用第二种判断
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这样,当我们遍历到nums[i]时,判断的是nums[i]前面的元素。如{-1, -1, 2},当遍历到第一个-1时,比较的是-1前面,第二个-1就不会忽略了。
对于b,c的去重,是要在找到满条件的三元组后。
// 时间复杂度: O(n^2)
// 空间复杂度: O(1)
class Solution {
public:
vector> threeSum(vector& nums) {
vector> res; // 二元数组,存储三元组
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); ++i) // 遍历数组,找到满足条件的三元组
{
if (nums[i] > 0) // 排序后,第一个元素大于0,不可能满足三个元素之和为0
{
return res;
}
if (i > 0 && nums[i] == nums[i - 1]) // 对第一个元素去重
{
continue;
}
int left = i + 1;
int right = nums.size() - 1; // 两个指针定位另外两个元素
while (right > left)
{
if (nums[i] + nums[left] + nums[right] > 0)
{
right--;
}
else if (nums[i] + nums[left] + nums[right] < 0)
{
left++;
}
else
{
res.push_back(vector{nums[i], nums[left], nums[right]}); // 满足条件的放入res中
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++; // 对第二、三个元素去重,一定要满足三数之和为0才进行去重
right--;
left++; // 找到答案后,两个指针同时收缩
}
}
}
return res;
}
};
题目链接:四数之和
视频讲解:难在去重和剪枝
跟三数之和类似,也需要用到双指针法,不过四数之和比三数之和多一个循环。
方法
在三数之和的基础上外面再加一层for循环,对于双指针left和right不变,两层for循环nums[i] + nums[j] 是确定值 ,然后移动left和right找到nums[i] + nums[j] + nums[left] +nums[right] = target。
同理,五数之和、六数之和等都用这种方法。
// 时间复杂度: O(n^3)
// 空间复杂度: O(1)
class Solution {
public:
vector> fourSum(vector& nums, int target) {
vector> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] > target && nums[i] >= 0) // 剪枝,不满足条件的
{
break;
}
if (i > 0 && nums[i] == nums[i - 1]) // 第一个元素去重
{
continue;
}
for (int j = i + 1; j < nums.size(); j++)
{
if (nums[j] + nums[i] >= 0 && nums[j] + nums[i] > target) // 第二重剪枝
{
break;
}
if (j > i + 1 && nums[j] == nums[j - 1]) // 第二个元素出重
{
continue;
}
int left = j + 1;
int right = nums.size() - 1;
while (right > left)
{
if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
{
right--;
}
else if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
{
left++;
}
else
{
res.push_back(vector{nums[i], nums[j], nums[left], nums[right]});
while (right > left && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--; // 第二、三个元素去重
left++;
right--;
}
}
}
}
return res;
}
};