454 四数相加Ⅱ
思考:
像个排列组合的题目(bushi
四个数组中找出四元组使它们的和为0,有点像字符异位相同匹配,也就是两个表元素进行对比,同时为了降低时间复杂度,要考虑将数组合并,比如前两个数组合并、后两个数组合并,这样合并后的循环时间复杂度为O(),即两个平方的加和。如果三个数组合并对比剩下一个数组,时间复杂度为O(
),不是最优合并。
题目有几个要注意的地方:
1、只需考虑满足条件的四元组的个数,不用输出具体的四元组;
2、四元组中每个位置上的数可以是重复的,不需要去重;
3、先将前两个数组求和(num1+num2),每次求和得到的结果存进哈希表中,便于后两个数组求和(num3+num4)后在哈希表中匹配是否存在四数之和为0。由于num1+num2+num3+num4=0,所以num3+num4=-(num1+num2),注意在后两个数组和与前两个数组和匹配时要将(num3+num4)取反。
我的代码:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//四个数组中找出四元组使它们的和为0,有点像字符串异位相同匹配
//为了降低时间复杂度,要考虑将数组合并,比如前两个数组合并、后两个数组合并
std::unordered_map<int,int> myMap;
//合并前两个数组,并统计两数组中数字之和以及它们的出现次数,存进map中
for(auto num1:nums1)
for(auto num2:nums2){
if(myMap.find(num1+num2)==myMap.end())
myMap.insert({num1+num2,1});
else {
auto cur=myMap.find(num1+num2);
cur->second++;
}
}
//关于后两个数组的和,查找map有无(a+b)能与之结合为0,并统计和为0的四元组的数量
int count=0;
for(auto num3:nums3)
for(auto num4:nums4){
if(myMap.find(-num3-num4)!=myMap.end()){
auto cur=myMap.find(-num3-num4);
count+=(cur->second);
}
}
return count;
}
};
- 时间复杂度: O(n^2)
- 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
383 赎金信
思考:这个和字符异位那题思路差不多,也是 有限个字符 这样的一个范围条件,所以还是用数组解决最简洁。
题目是判断ran的字符是否能够用mag中的字符完全表示,用集合来表示就是ran是mag的子集,mag的内容包含ran的内容。所以还是要先将ran的字符保存进26个小写字母的数组result中,记录ran中的字符。
然后再遍历mag,并判断遍历到的字符是否在result中有所记录,若有记录则使记录的数量减一。
最后判断所有记录都被清零的话,意味着ran就是mag的子集,即ran能由mag里的字符构成
这样设计也能满足题目“magazine
中的每个字符只能在 ransomNote
中使用一次”的要求。
我的代码:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int result[26]={0};
int count=0;
//题目是判断ran的字符是否能够用mag中的字符完全表示,用集合来表示就是ran是mag的子集,mag的内容包含ran的内容
//所以还是要先将ran的字符保存进26个小写字母的数组result中,记录ran中的字符
for(int i=0;i<ransomNote.size();i++)
result[ransomNote[i]-'a']++;
//再遍历mag,并判断遍历到的字符是否在result中有所记录,若有记录则使记录的数量减一
for(int i=0;i<magazine.size();i++){
if(result[magazine[i]-'a']>0){
result[magazine[i]-'a']--;
}
}
//最后判断所有记录都被清零的话,意味着ran就是mag的子集,即ran能由mag里的字符构成
for(int i=0;i<26;i++){
if(result[i])
return false;
}
return true;
}
};
15 三数之和
思考:
哈希表写多了,看到这道题一上来就想用哈希表解决,然后bug一改就老实了。
这道题由于要求答案不能包含重复三元组,所以和四数相加不一样。必须要考虑去重问题。但是用哈希表写关于去重的细节很多,很难一下子考虑完全(反正我没完全通过所有样例)。
看了卡哥的解法给了一个解题的新思路,双指针法居然还能这么用.jpg
双指针法
(其实也不一定是严格的两个指针,也要灵活考虑加入一到两个指针)
三数之和,可以想到用三个指针指向数组中不重复的三个元素,来判断三者相加和是否为0。首先先将数组排为升序,第一个数的指针i可以看作数组的动态边界起点,不断前进,剩下两个指针left和right可以看作移动的滑动窗口。
当第一个数当前取某个值时,由于数组已变为有序数组,数组左边的元素一定较小,右边的元素一定较大,三数之和大于0时则right向左移动、小于0则left向右移动,left和right就不断向内缩小,直到取完所有合适的三元组,然后再迭代第一个数,重复滑动窗口的操作。
其中去重的要点,一定要先将数组排序,这样一来数值相同的元素就会集中一起,便于相同元素跳过。当指针指向的元素周围有相同的元素,进行去重跳过判断的条件是:
//第一个数字去重
if(i>0&&nums[i]==nums[i-1]) continue;
//left和right去重
while(right>left&&nums[right]==nums[right-1]) right--;
while(right>left&&nums[left]==nums[left+1]) left++;
对于第一个数,判断位置是i和i-1的含义是,当前元素和前一个已遍历过的元素是否相同,意味着上一个元素或已被作为有效三元组push_back进结果数组中,或已被当作重复元素跳过,也就是上一个相同元素已经被操作过了,因此无需再考虑当前元素,可跳过。
但滑动窗口两端的两个数则是指针与窗口内未访问的元素进行比较,相同则继续向内缩小范围。
注意不能有重复的三元组是指,不能出现所含元素完全相同的三元组,即不能为{[-1,0,1],[-1,1,0]},但是三元组中可以有数据重复(但下标不同)的元素,即{[0,0,0],[-1,-1,2]}。所以第一个数去重的判断不是i和i+1原因为:如果逻辑是 下一个元素和当前元素相同则不将当前元素放入结果数组考虑中,则不满足“三元组中可以有数据重复但下标不同的y”
我的代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//梦碎了。。。
std::vector<vector<int>> result={};
//用指针法,必须先将数组排序
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if (nums[0] > 0)
break;
//对第一个数去重
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 {
result.push_back({nums[i],nums[left],nums[right]});
//对left和right指向的两个数去重
while(right>left&&nums[right]==nums[right-1]) right--;
while(right>left&&nums[left]==nums[left+1]) left++;
right--;
left++;
}
}
}
return result;
}
};
/*
没法实现去重的哈希版本:
vector<vector<int>> threeSum(vector<int>& nums) {
std::multimap<int,int> myMap;
std::vector<vector<int>> result={};
for(int i=0;i<nums.size();i++)
myMap.insert({nums[i],i});
for(int i=0;i<nums.size();i++)
for(int j=i+1;j<nums.size();j++){
int sum=nums[i]+nums[j];
//sumMap.insert({sum,(i,j)});
auto Find=myMap.find(-sum);
auto Count = myMap.count(-sum);
while (Count) {
if (Find != myMap.end() && Find->second > j)
result.push_back({ nums[i],nums[j],Find->first });
Find++;
Count--;
}
}
return result;
vector<vector<int>> threeSum(vector<int>& nums) {
std::multimap<int,pair<int,int>> sumMap;
std::vector<vector<int>> result;
for(int i=0;i<nums.size();i++)
for(int j=i+1,j<nums.size(),j++){
int sum=nums[i]+nums[j];
sumMap.insert({sum,(i,j)});
}
for(auto sums:sumMap){
if(sums.find())
}
}*/
*由于第一个数的遍历必须占用一个循环,后两个数又可以优化为双指针法,也只用占用一个循环,因此可以将时间复杂度从三重循环嵌套的O()降低为O(
)。
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
18 四数之和
思考:
这题在熟悉三数之和以后就很容易想到嵌套循环+双指针法:双指针遍历部分必定是多元组的最后两个数,且双指针法的时间复杂度都是O(n),即一层循环。但是既然是四数之和,自然迭代的指针也从三数之和的一个自由指针变成两个,所以也很容易可以得到四数之和的时间复杂度为O()。
有个易错点:自然迭代指针的去重操作是continue,直接跳过本次循环,而不是指针++。
我的代码:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
std::vector<vector<int>> result = {};
sort(nums.begin(), nums.end());
//和三数之和有点相似的地方就是,数组上仍然设置一个区域使用双指针遍历,且仍然指向四元组中的后两个数
//第一个指针仍然从头开始自然迭代遍历整个数组
for (int s1 = 0; s1 < nums.size();s1++) {
//if (nums[0] > target)
// break;
//第一个数的去重操作,注意是continue直接跳过本次循环,不是s1++
if (s1 > 0 && nums[s1] == nums[s1 - 1]) continue;
//第二个指针尚未进行双指针遍历,所以还是和s1一样自然迭代
for (int s2 = s1+1; s2 < nums.size();s2++) {
//第二个数的去重操作
if (s2>s1+1 && nums[s2] == nums[s2 - 1]) continue;
int b1= s2 + 1, b2 = nums.size() - 1;
//从这里开始双指针遍历
while (b2>b1) {
int sum1=nums[s1] + nums[s2];
long sum2=(long)target-nums[b1] - nums[b2];
if ( sum1>sum2) b2--;
else if (sum1<sum2) b1++;
else {
result.push_back({ nums[s1],nums[s2],nums[b1],nums[b2] });
while (b2 > b1 && nums[b2] == nums[b2 - 1]) b2--;
while (b2 > b1 && nums[b1] == nums[b1 + 1]) b1++;
b2--;
b1++;
}
}
}
}
return result;
}
};
- 时间复杂度: O(n^3)
- 空间复杂度: O(1)
*文章是本人刷题过程中的一些笔记和理解,记录的解析不一定足够清晰,也可能存在本人暂未意识到的错误,如有问题欢迎大家指出。文章中学习到的解法来自代码随想录的B站视频(哈希表4~6)以及代码随想录的学习网站