1 题目
给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
示例 1:
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。
示例 2:
输入:s = "aeiou", k = 2
输出:2
解释:任意长度为 2 的子字符串都包含 2 个元音字母。
示例 3:
输入:s = "leetcode", k = 3
输出:2
解释:"lee"、"eet" 和 "ode" 都包含 2 个元音字母。
示例 4:
输入:s = "rhythms", k = 4
输出:0
解释:字符串 s 中不含任何元音字母。
示例 5:
输入:s = "tryhard", k = 4
输出:1
提示:
1 <= s.length <= 10^5s由小写英文字母组成1 <= k <= s.length
2 代码实现
cpp
class Solution {
public:
int maxVowels(string s, int k) {
unordered_set<char> vowels ={'a' , 'e' , 'i' ,'o' ,'u'};
unordered_map<char , int > window ;
int left = 0 ;
int right = 0 ;
int max_count = 0 ;
int current_vowel_total = 0 ;
while(right < s.size()){
char c = s[right];
if(vowels.count(c)){
window[c]++;
current_vowel_total++;
}
right++;
while(right - left == k ){
max_count = max (max_count,current_vowel_total);
char d = s[left];
if (vowels.count(d)){
window[d]--;
current_vowel_total--;
if(window[d] == 0){
window.erase(d);
}
}
left++;
}
}
return max_count;
}
};
完整的代码:
cpp
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxVowels(string s, int k) {
// 定义元音集合,用于快速判断字符是否为元音
unordered_set<char> vowels = {'a', 'e', 'i', 'o', 'u'};
// 用 map 记录窗口内各元音字母的出现次数(key:元音字符,value:出现次数)
unordered_map<char, int> window;
int left = 0, right = 0;
int max_count = 0;
int current_vowel_total = 0; // 辅助变量:窗口内元音总数(避免每次遍历 map 求和)
while (right < s.size()) {
// 1. 右移窗口:将当前字符加入窗口
char c = s[right];
if (vowels.count(c)) { // 仅统计元音字母
window[c]++; // map 中记录该元音的出现次数
current_vowel_total++; // 元音总数同步增加
}
right++; // 增大窗口
// 2. 窗口内数据更新(已通过 map 和 current_vowel_total 维护)
// 3. 判断窗口是否达到定长k:达到后收缩左侧,并更新最大值
while (right - left == k) {
// 更新最大元音数(当前窗口是有效定长窗口)
max_count = max(max_count, current_vowel_total);
// 4. 左移窗口:将左侧字符移出窗口
char d = s[left];
if (vowels.count(d)) { // 若移出的是元音
window[d]--; // map 中对应计数减1
current_vowel_total--; // 元音总数同步减少
// 若该元音计数为0,可从map中删除(可选,不影响结果)
if (window[d] == 0) {
window.erase(d);
}
}
left++; // 缩小窗口
}
}
return max_count;
}
};
// 测试代码
int main() {
Solution sol;
cout << sol.maxVowels("abciiidef", 3) << endl; // 输出 3
cout << sol.maxVowels("aeiou", 2) << endl; // 输出 2
cout << sol.maxVowels("leetcode", 3) << endl; // 输出 2
cout << sol.maxVowels("rhythms", 4) << endl; // 输出 0
cout << sol.maxVowels("tryhard", 4) << endl; // 输出 1
return 0;
}
核心说明(严格遵循滑动窗口框架 + map 用法):
算法笔记 14 滑动窗口-CSDN博客(含框架模板)
框架模板:
cpp
// 滑动窗口算法伪码框架
void slidingWindow(string s) {
// 用合适的数据结构记录窗口中的数据,根据具体场景变通
// 比如说,我想记录窗口中元素出现的次数,就用 map
// 如果我想记录窗口中的元素和,就可以只用一个 int
auto window = ...
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
window.add(c);
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
// *** debug 输出的位置 ***
printf("window: [%d, %d)\n", left, right);
// 注意在最终的解法代码中不要 print
// 因为 IO 操作很耗时,可能导致超时
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
window.remove(d);
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
1. 窗口数据结构:unordered_map<char, int>
- key:窗口内的元音字符(非元音字符不存入 map)
- value:对应元音字符在窗口内的出现次数
- 作用:严格按照要求用 map 记录窗口内的相关数据(此处为元音的频次)
2. 辅助变量 current_vowel_total
- 用于快速获取窗口内元音总数,避免每次需要统计时遍历 map 求和(优化效率)
- 与 map 数据严格同步:加入元音时 map 计数 +1、总数 +1;移出元音时 map 计数 -1、总数 -1
3. 滑动窗口核心流程(完全匹配框架):
| 步骤 | 操作 | 细节 |
|---|---|---|
| 窗口扩张 | 右指针右移 | 若字符是元音,更新 map 计数和总数 |
| 窗口内更新 | 维护 map 和总数 | 同步数据,无需额外逻辑 |
| 收缩条件 | right - left == k |
定长窗口,达到长度则必须收缩左侧 |
| 窗口收缩 | 左指针右移 | 若字符是元音,更新 map 计数(计数为 0 时可删除 key)和总数 |
| 结果更新 | 收缩前记录最大值 | 确保每个定长窗口的元音数都被考虑 |
4. 时间复杂度与空间复杂度
- 时间复杂度:O (n),n 为字符串长度。每个字符仅被左右指针各访问一次,map 操作(插入、删除、修改)均为 O (1)(哈希表平均情况)
- 空间复杂度:O (1),map 中最多存储 5 个 key(元音字母共 5 种),属于常数级空间