D10 454. 四数相加Ⅱ 383. 赎金信
454 四数相加Ⅱ(力扣:[https://leetcode.cn/problems/4sum-ii/])
条件:给定四个长度均为n的数组,要满足nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0,而元组{ i, j, k, l }不重复,返回所有符合条件的元组数量;
Tips:
- 为了降低时间复杂度,将原问题的分别查找四个元组改成两两一组,分为两两是因为另一种分法为一三,时间复杂度依然高;分组不限顺序(AB+CD或者AC+BD...)这里为了方便分为AB+CD,这样就将原求解式变成了A+B = 0 - (C+D)→ new1 = 0 - new2,其中new1 = A+B、new2 = C+D,new1、new2为新set的key;由于new1为和、也就是一个key可能会对应多个AB的值,所以每个new1的value统计有多少可以算出当前key的AB组合;
- 注意n为索引/下标,nums1[n] + nums2[n] 才是参与凑0计算的值;而在迭代器遍历中,for( int n1 : nums1 )中的n1为元素nums1[i]而非下标i值!
- (以n1为元素而非下标为前提)访问key/value的方法在代码里展示了两种,一种是key = n1+n2, value = abSum[ n1+n2 ];另一种是使用find函数,但如果找不到target从而返回abSum.end()会导致未定义行为报错, 因此多定义一个auto it = abSum.find( n1+n2 ),在it != abSum.end()时再使用it->second为value,->first或n1+n2为key;
代码来自AI:
点击查看代码
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//先统计n1+n2所有可能的sum的情况,key为sum、value为sum出现的次数
unordered_map<int,int>abSum;
int n = nums1.size();
//数组长度相同,遍历时用一个数组取size即可
for( int n1 = 0; n1 < n; n1++ ){
//每个num1的元素算一遍和所有nums2元素的sum
for( int n2 = 0; n2 < n; n2++ ){
//当前sum为新sum时,存入新key同时设置value为1
if( abSum.find( nums1[n1] + nums2[n2] ) != abSum.end() ){
abSum[ nums1[n1] + nums2[n2] ]++;
}else{
//当该sum存在时,更新该sum的value+1
abSum.insert({nums1[n1] + nums2[n2], 1});
}
}
}
int ans = 0;
//统计结束后挨个计算target = 0 - nums3[?] -nums4[?]
for( int n3 : nums3 ){
for( int n4 : nums4 ){
//这种写法n3对应的是nums3[?]而非下标?,因此计算target时直接用n3和n4
int target = 0 - n3 - n4;
//如果能在abSum里找到对应的target
if( abSum.find(target) != abSum.end() ){
//将结果计数值叠加当前target的value
ans += abSum[target];
}
//或者使用迭代器:
//auto it = abSum.find(target)
//if( it != abSum.end() ){ ans += it->second; }
}
}
return ans;
}
};
383 赎金信(力扣:[https://leetcode.cn/problems/ransom-note/])
条件:给定两个只有小写字母的字符串AB,要求在字符串B中找到足够数量的组成A的字母,B中每个字母只能使用一次,成功返回true;
Tips:
- 原本的思路是使用一个map,key储存A里的字母减去'a'的值,value存储该值出现次数,再遍历B查询B的每个元素是否在A中出现,每出现一次将A的value减一,最后再查询A中是否还有value非0的元素;这种方法比较复杂,原因为map还要额外做红黑树和哈希函数、数据量大的时候会比较耗时,可以直接使用数组或者字符串操作;参考链接
- 下面使用数组的方法先遍历了A进行record++,再遍历B进行record--,最后遍历A确认没有record>0, 而上面的参考先遍历B进行record++,而后遍历A进行record--,只要出现<0则返回false,这种写法比前一种少了一遍遍历;
使用数组:
点击查看代码
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//使用数组会有“下标”和“元素值”两个部分可以定义,这里依旧采用字母异位词的思路,使用字母减去'a'作为下标,元素值存储该字母出现次数;
int record[26] = {};
//题目需要考虑字母数量,所以如果magazine字符总数少于ransome则直接返回false
if( magazine.length() < ransomNote.length() ){
return false;
}
//遍历ransomNote进行存储
for( int tmp = 0; tmp < ransomNote.length(); tmp++ ){
record[ ransomNote[tmp] - 'a' ]++;
}
//遍历magazine
for( int tmp = 0; tmp < magazine.length(); tmp++ ){
record[ magazine[tmp] - 'a' ]--;
}
//再次遍历ransomNote,如果存在值>0则返回false
for( int tmp = 0; tmp < 26; tmp++ ){
if( record[tmp] > 0 ){
return false;
}
}
return true;
}
};
另一种使用字符串操作, 注意要内层遍历需要被执行删除的A字符串,这样能保证B字符串一直被按照顺序遍历、没有跳过元素:
点击查看代码
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
for (int i = 0; i < magazine.length(); i++) {
for (int j = 0; j < ransomNote.length(); j++) {
// 在ransomNote中找到和magazine相同的字符
if (magazine[i] == ransomNote[j]) {
ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符
break;
}
}
}
// 如果ransomNote为空,则说明magazine的字符可以组成ransomNote
if (ransomNote.length() == 0) {
return true;
}
return false;
}
};
浙公网安备 33010602011771号