10.16-10.25力扣计数刷题
计数相关知识:
1.加法原理
如果完成一件事有n类方法,第i类方法有mᵢ种方式,则总方法数为:总方法数 = m₁ + m₂ + ... + mₙ
2.乘法原理
如果完成一件事需要n个步骤,第i步有mᵢ种方式,则总方法数为:总方法数 = m₁ × m₂ × ... × mₙ
3.排列
从n个不同元素中取出m个元素按顺序排列:P(n, m) = n! / (n-m)!。示例:5个人选3个排成一排:P(5,3) = 5!/(5-3)! = 60
4.组合
从n个不同元素中取出m个元素不考虑顺序:C(n, m) = n! / (m! × (n-m)!)
【1】594. 最长和谐子序列
日期:10.16
1.题目链接:594. 最长和谐子序列 - 力扣(LeetCode)
https://leetcode.cn/problems/longest-harmonious-subsequence/description/?envType=problem-list-v2&envId=counting
2.题目类型:数组,哈希表,排序,滑动窗口
3.方法一:滑动窗口(一次题解)
先对数组进行排序,这样相同的数字会聚集在一起
使用滑动窗口技术找到满足条件的最长子数组
和谐子序列的条件:窗口内最大值 - 最小值 = 1
begin 指向第一个连续相同元素的子序列的第一个元素,end 指向相邻的第二个连续相同元素的子序列的末尾元素,如果满足二者的元素之差为 1,则当前的和谐子序列的长度即为两个子序列的长度之和,等于 end−begin+1。
关键代码:
sort(nums.begin(),nums.end());int sum=0;int begin=0;// 滑动窗口的起始位置for(int end=0;end<nums.size();end++){// 当窗口内最大值与最小值的差超过1时,收缩窗口while(nums[end]-nums[begin]>1){begin++;}if(nums[end]-nums[begin]==1){sum=max(sum,end-begin+1);}}
4.方法二:哈希表(半解)
首先遍历一遍数组,得到哈希映射。随后遍历哈希映射,设当前遍历到的键值对为 (x,value),那么就查询 x+1 在哈希映射中对应的统计次数,就得到了 x 和 x+1 出现的次数,和谐子序列的长度等于 x 和 x+1 出现的次数之和。
关键代码:
for(int num:nums){cnt[num]++;} //遍历哈希表,检查相邻数字for(auto [key, val]:cnt){if(cnt.count(key+1)){// 更新结果为当前数字和相邻数字频率和的最大值res=max(res,val+cnt[key+1]);}}
【2】811. 子域名访问计数
日期:10.17
1.题目链接:811. 子域名访问计数 - 力扣(LeetCode)
https://leetcode.cn/problems/subdomain-visit-count/description/?envType=problem-list-v2&envId=counting
2.题目类型:数组,哈希表,字符串,计数
3.方法一:哈希表(半解)
为了获得每个子域名的计数配对域名,需要使用哈希表记录每个子域名的计数。遍历数组 cpdomains,对于每个计数配对域名,获得计数和完整域名,更新哈希表中的每个子域名的访问次数。
遍历数组 cpdomains 之后,遍历哈希表,对于哈希表中的每个键值对,关键字是子域名,值是计数,将计数和子域名拼接得到计数配对域名,添加到答案中。
substr(0, space) 从字符串 cpdomain 中提取子字符串
stoi() 是 "string to integer" 的缩写,将字符串转换为整数,会自动处理前导空格和符号
关键代码:
for(auto &&cpdomain:cpdomains){// 分离数字和域名int space=cpdomain.find(' ');//找到空格位置,分离数字部分和域名部分int count=stoi(cpdomain.substr(0, space));//将数字字符串转换为整数string domain=cpdomain.substr(space + 1);//获取域名字符串 // 统计完整域名counts[domain]+=count; // 统计所有子域名for(int i=0;i<domain.size();i++){if(domain[i]=='.'){string subdomain=domain.substr(i+1);counts[subdomain]+=count;}}} // 将统计结果转换为输出格式for(auto &&[subdomain,count]:counts){ans.emplace_back(to_string(count)+" "+subdomain);}
【3】819. 最常见的单词
日期:10.18
1.题目链接:819. 最常见的单词 - 力扣(LeetCode)
https://leetcode.cn/problems/most-common-word/description/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,计数
3.方法一:哈希表+计数(半解)
需要使用哈希集合存储禁用单词列表中的单词。
遍历段落 paragraph,得到段落中的所有单词,并对每个单词计数,使用哈希表记录每个单词的计数。由于每个单词由连续的字母组成,因此当遇到一个非字母的字符且该字符的前一个字符是字母时,即为一个单词的结束,如果该单词不是禁用单词,则将该单词的计数加 1。如果段落的最后一个字符是字母,则当遍历结束时需要对段落中的最后一个单词判断是否为禁用单词,如果不是禁用单词则将次数加 1。
在遍历段落的过程中,对于每个单词都会更新计数,因此遍历结束之后即可得到最大计数,即出现次数最多的单词的出现次数。遍历段落之后,遍历哈希表,寻找出现次数等于最大计数的单词,该单词即为最常见的单词。
isalpha() 判断字符是否为字母
tolower() 将字符转换为小写,实现大小写不敏感
关键代码:
// 遍历段落,提取单词并统计频率for(int i=0;i<=length;i++){// 如果是字母字符,添加到当前单词(转换为小写)if(i<length&&isalpha(paragraph[i])){word.push_back(tolower(paragraph[i]));} // 如果遇到非字母字符或到达结尾,且当前单词不为空else if(word.size()>0){// 如果单词不在禁用列表中if(!bannedSet.count(word)){frequencies[word]++; maxFrequency=max(maxFrequency,frequencies[word]); // 更新最大频率}word=""; // 重置当前单词}}
【4】884. 两句话中的不常见单词
日期:10.19
1.题目链接:884. 两句话中的不常见单词 - 力扣(LeetCode)
https://leetcode.cn/problems/uncommon-words-from-two-sentences/description/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,计数
3.方法一:哈希表(官方题解)
找出在两个句子中一共只出现一次的单词。
因此我们可以使用一个哈希映射统计两个句子中单词出现的次数。对于哈希映射中的每个键值对,键表示一个单词,值表示该单词出现的次数。在统计完成后,我们再对哈希映射进行一次遍历,把所有值为 1 的键放入答案中即可。
stringstream 自动按空格分割单词
move(word) 使用移动语义提高性能
关键代码:
// 定义lambda函数用于分割句子并统计单词频率auto insert=[&](const string& s){stringstream ss(s); // 创建字符串流string word;while(ss>>word) { // 自动按空格分割单词++freq[move(word)]; // 移动语义,避免不必要的拷贝}};insert(s1);insert(s2);// 收集只出现一次的单词vector<string> ans;for(const auto& [word, occ]:freq){if(occ==1){ans.push_back(word);}}
【5】1010. 总持续时间可被 60 整除的歌曲
日期:10.20
1.题目链接:1010. 总持续时间可被 60 整除的歌曲 - 力扣(LeetCode)
https://leetcode.cn/problems/pairs-of-songs-with-total-durations-divisible-by-60/description/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,计数,组合计数
3.方法一:组合数学(半解)
每首歌曲对结果的影响因素是它的持续时间除以 60 后的余数。可以用一个长度为 60 的数组 cnt,用来表示余数出现的次数。然后分情况统计歌曲对:

关键代码:
vector<int> cnt(60);for(int t:time){cnt[t%60]++; // 统计每个时间对60取模的余数} long long res=0; // 使用long long防止整数溢出 // 处理余数1到29的情况(与59到31配对)for(int i=1;i<30;i++){res+=cnt[i]*cnt[60-i]; // 配对数量 = 余数i的数量 × 余数(60-i)的数量}// 处理特殊情况:余数0和余数30res+=(long long)cnt[0]*(cnt[0]-1)/2+ // 余数0内部配对:组合数C(n,2)(long long)cnt[30]*(cnt[30]-1)/2; // 余数30内部配对:组合数C(n,2)
【6】1079. 活字印刷
日期:10.21
1.题目链接:1079. 活字印刷 - 力扣(LeetCode)
https://leetcode.cn/problems/letter-tile-possibilities/description/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,回溯,深度优先搜索
3.方法一:回溯(官方题解)
使用深度优先搜索(DFS)生成所有可能的序列
通过字符计数确保不会超过可用字符的数量
使用回溯法探索所有可能的排列组合
关键代码:
// DFS函数:递归生成所有可能的序列int dfs(unordered_map<char,int>& count,set<char>& tile,int i){// 基线条件:没有剩余字符可用if(i==0){return 1; }int res=1; // 从1开始,表示当前序列for(char t:tile){if(count[t]>0){ count[t]--; // 使用一个该字符res+=dfs(count,tile,i-1); // 递归处理剩余字符count[t]++; // 回溯,恢复字符计数}}
【7】1090. 受标签影响的最大值
日期:10.22
1.题目链接:1090. 受标签影响的最大值 - 力扣(LeetCode)
https://leetcode.cn/problems/largest-values-from-labels/solutions/2278874/shou-biao-qian-ying-xiang-de-zui-da-zhi-5h9ll/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,计数,排序
3.方法一:排序 + 哈希表(一次题解)
首先将元素按照 values 的值进行降序排序。待排序完成后,依次遍历每个元素并判断是否选择。使用一个变量 choose 记录已经选择的元素个数,以及一个哈希表记录每一种标签已经选择的元素个数(键表示标签,值表示该标签已经选择的元素个数):
如果 choose=numWanted,直接退出遍历;
如果当前元素的标签在哈希表中对应的值等于 useLimit,忽略这个元素,否则选择这个元素,并更新 choose、哈希表以及答案。
关键代码:
for(int i=0;i<n&&choose<numWanted;++i){int label=labels[id[i]];// 如果该标签已达到使用限制,跳过if(cnt[label]==useLimit){continue;}// 选择当前物品++choose;ans+=values[id[i]];++cnt[label];}
【8】1160. 拼写单词
日期:10.23
1.题目链接:1160. 拼写单词 - 力扣(LeetCode)
https://leetcode.cn/problems/find-words-that-can-be-formed-by-characters/description/?envType=problem-list-v2&envId=counting
2.题目类型:哈希表,字符串,计数
3.方法一:哈希表(一次题解)
只需要用一个哈希表存储 chars 中每个字母的数量,再用一个哈希表存储 word 中每个字母的数量,最后将这两个哈希表的键值对逐一进行比较即可。
关键代码:
for(string word : words){// 统计当前单词中每个字符的出现频率unordered_map<char,int> word_cnt;for(char c:word){++word_cnt[c];} // 检查当前单词是否可以用 chars 拼写bool is_ans=true;for(char c:word){// 如果 chars 中某个字符的数量不足if(chars_cnt[c]<word_cnt[c]){is_ans=false;break;}} // 如果可以拼写,累加单词长度if(is_ans){ans+=word.size();}
【9】1189. “气球” 的最大数量
日期:10.24
1.题目链接:1189. “气球” 的最大数量 - 力扣(LeetCode)
https://leetcode.cn/problems/maximum-number-of-balloons/description/?envType=problem-list-v2&envId=counting
2.题目类型:字符串,计数
3.方法一:统计(一次题解)
构成单词 "balloon" 需要 1 个字母 ‘b’ 、1 个字母 ‘a’ 、2 个字母 ‘l’ 、2 个字母 ‘o’ 、1 个字母 ‘n’,因此只需要统计字符串中字母 ‘a’,‘b’,‘l’,‘o’,‘n’ 的数量即可。其中每个字母 "balloon" 需要两个 ‘l’,‘o’,可以将字母 ‘l’,‘o’ 的数量除以 2,返回字母 ‘a’,‘b’,‘l’,‘o’,‘n’ 中数量最小值即为可以构成的单词数量。
关键代码:
for(auto & ch: text){if(ch=='b'){cnt[0]++;}else if(ch=='a'){cnt[1]++;}else if(ch=='l'){cnt[2]++;}else if(ch=='o'){cnt[3]++;}else if(ch=='n'){cnt[4]++;}}cnt[2]/=2;cnt[3]/=2;
【10】1221. 分割平衡字符串
日期:10.25
1.题目链接:1221. 分割平衡字符串 - 力扣(LeetCode)
https://leetcode.cn/problems/split-a-string-in-balanced-strings/description/?envType=problem-list-v2&envId=counting
2.题目类型:贪心,字符串,计数
3.方法一:贪心(半解)
根据题意,对于一个平衡字符串 s,若 s 能从中间某处分割成左右两个子串,若其中一个是平衡字符串,则另一个的 L 和 R 字符的数量必然是相同的,所以也一定是平衡字符串。
为了最大化分割数量,可以不断循环,每次从 s 中分割出一个最短的平衡前缀,由于剩余部分也是平衡字符串,将其当作 s 继续分割,直至 s 为空时,结束循环。
代码实现中,可以在遍历 s 时用一个变量 d 维护 L 和 R 字符的数量之差,当 d=0 时就说明找到了一个平衡字符串,将答案加一。
关键代码:
int ans=0,d=0;for(char ch : s){ch=='L'?++d:--d;if(d==0){++ans;}}
