本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
示例 1:
java
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。
示例 2:
java
输入:s = "aeiou", k = 2
输出:2
解释:任意长度为 2 的子字符串都包含 2 个元音字母。
示例 3:
java
输入:s = "leetcode", k = 3
输出:2
解释:"lee"、"eet" 和 "ode" 都包含 2 个元音字母。
示例 4:
java
输入:s = "rhythms", k = 4
输出:0
解释:字符串 s 中不含任何元音字母。
示例 5:
java
输入:s = "tryhard", k = 4
输出:1
提示:
1 <= s.length <= 10^5s由小写英文字母组成1 <= k <= s.length
解法 定长滑窗
我们要计算所有长度恰好为 k k k 的子串中,最多可以包含多少个元音字母。
暴力枚举所有子串?时间复杂度是 O ( n k ) O(nk) O(nk) ,太慢了。能否 O ( 1 ) O(1) O(1) 计算子串的元音个数?
这是可以做到的,对于下图的字符串 a b c i abci abci ,假如我们已经计算出了子串 a b c abc abc 的元音个数,那么从子串 a b c abc abc 到子串 b c i bci bci ,只需要考虑移除(离开窗口)的字母 a a a 是不是元音,以及添加(进入窗口)的字母 i i i 是不是元音即可,因为中间的字母 b b b 和 c c c 都在这两个子串中。

定长滑窗套路
窗口右端点在 i i i 时,由于窗口长度为 k k k ,所以窗口左端点为 i − k + 1 i - k + 1 i−k+1 。总结为三步:入-更新-出。
- 入 :下标为 i i i 的元素进入窗口,更新相关统计量 。如果窗口左端点 i − k + 1 < 0 i - k + 1 < 0 i−k+1<0 ,则尚未形成第一个窗口,重复第一步。
- 更新:更新答案,一般是更新最大值/最小值。
- 出 :到第三步则说明窗口左端点 i − k + 1 ≥ 0 i - k + 1\ge 0 i−k+1≥0 ,因此需提前一步让下标为 i − k + 1 i - k+1 i−k+1 的元素离开窗口,更新相关统计量 ,为下一个循环的入做准备。
以上三步适用于所有定长滑窗题目。
java
class Solution {
public int maxVowels(String S, int k) {
char[] s = S.toCharArray();
int ans = 0;
int vowel = 0;
for (int i = 0; i < s.length; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = Math.max(ans, vowel);
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
}
cpp
class Solution {
public:
int maxVowels(string s, int k) {
int ans = 0, vowel = 0;
for (int i = 0; i < s.size(); i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = max(ans, vowel);
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
};
python
class Solution:
def maxVowels(self, s: str, k: int) -> int:
ans = vowel = 0
for i, c in enumerate(s): # 枚举窗口右端点 i
# 1. 右端点进入窗口
if c in "aeiou":
vowel += 1
left = i - k + 1 # 窗口左端点
if left < 0: # 窗口大小不足 k,尚未形成第一个窗口
continue
# 2. 更新答案
ans = max(ans, vowel)
# 3. 左端点离开窗口,为下一个循环做准备
if s[left] in "aeiou":
vowel -= 1
return ans
rust
impl Solution {
pub fn max_vowels(s: String, k: i32) -> i32 {
let s = s.as_bytes();
let k = k as usize;
let mut ans = 0;
let mut vowel = 0;
for (i, &c) in s.iter().enumerate() { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if c == b'a' || c == b'e' || c == b'i' || c == b'o' || c == b'u' {
vowel += 1;
}
if i + 1 < k { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = ans.max(vowel);
// 3. 左端点离开窗口,为下一个循环做准备
let out = s[i + 1 - k];
if out == b'a' || out == b'e' || out == b'i' || out == b'o' || out == b'u' {
vowel -= 1;
}
}
ans
}
}
go
func maxVowels(s string, k int) (ans int) {
vowel := 0
for i, in := range s { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if in == 'a' || in == 'e' || in == 'i' || in == 'o' || in == 'u' {
vowel++
}
left := i - k + 1 // 窗口左端点
if left < 0 { // 窗口大小不足 k,尚未形成第一个窗口
continue
}
// 2. 更新答案
ans = max(ans, vowel)
// 3. 左端点离开窗口,为下一个循环做准备
out := s[left]
if out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u' {
vowel--
}
}
return
}
js
var maxVowels = function(s, k) {
let ans = 0, vowel = 0;
for (let i = 0; i < s.length; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] === 'a' || s[i] === 'e' || s[i] === 'i' || s[i] === 'o' || s[i] === 'u') {
vowel++;
}
const left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = Math.max(ans, vowel);
// 3. 左端点离开窗口,为下一个循环做准备
let out = s[left];
if (out === 'a' || out === 'e' || out === 'i' || out === 'o' || out === 'u') {
vowel--;
}
}
return ans;
};
c
#define MAX(a, b) ((b) > (a) ? (b) : (a))
int maxVowels(char* s, int k) {
int ans = 0, vowel = 0;
for (int i = 0; s[i]; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = MAX(ans, vowel);
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
优化
答案的最大值是 k k k ,即窗口中全部是元音。如果发现答案等于 k k k ,由于答案无法再大,可退出循环。
java
class Solution {
public int maxVowels(String S, int k) {
char[] s = S.toCharArray();
int ans = 0;
int vowel = 0;
for (int i = 0; i < s.length; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = Math.max(ans, vowel);
if (ans == k) { // 答案已经等于理论最大值
break; // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
}
cpp
class Solution {
public:
int maxVowels(string s, int k) {
int ans = 0, vowel = 0;
for (int i = 0; i < s.size(); i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = max(ans, vowel);
if (ans == k) { // 答案已经等于理论最大值
break; // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
};
python
impl Solution {
pub fn max_vowels(s: String, k: i32) -> i32 {
let s = s.as_bytes();
let k = k as usize;
let mut ans = 0;
let mut vowel = 0;
for (i, &c) in s.iter().enumerate() { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if c == b'a' || c == b'e' || c == b'i' || c == b'o' || c == b'u' {
vowel += 1;
}
if i + 1 < k { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = ans.max(vowel);
// 3. 左端点离开窗口,为下一个循环做准备
let out = s[i + 1 - k];
if out == b'a' || out == b'e' || out == b'i' || out == b'o' || out == b'u' {
vowel -= 1;
}
}
ans
}
}
rust
impl Solution {
pub fn max_vowels(s: String, k: i32) -> i32 {
let s = s.as_bytes();
let k = k as usize;
let mut ans = 0;
let mut vowel = 0;
for (i, &c) in s.iter().enumerate() { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if c == b'a' || c == b'e' || c == b'i' || c == b'o' || c == b'u' {
vowel += 1;
}
if i + 1 < k { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = ans.max(vowel);
if ans == k { // 答案已经等于理论最大值
break; // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
let out = s[i + 1 - k];
if out == b'a' || out == b'e' || out == b'i' || out == b'o' || out == b'u' {
vowel -= 1;
}
}
ans as _
}
}
go
func maxVowels(s string, k int) (ans int) {
vowel := 0
for i, in := range s { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if in == 'a' || in == 'e' || in == 'i' || in == 'o' || in == 'u' {
vowel++
}
left := i - k + 1 // 窗口左端点
if left < 0 { // 窗口大小不足 k,尚未形成第一个窗口
continue
}
// 2. 更新答案
ans = max(ans, vowel)
if ans == k { // 答案已经等于理论最大值
break // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
out := s[left]
if out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u' {
vowel--
}
}
return
}
js
var maxVowels = function(s, k) {
let ans = 0, vowel = 0;
for (let i = 0; i < s.length; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] === 'a' || s[i] === 'e' || s[i] === 'i' || s[i] === 'o' || s[i] === 'u') {
vowel++;
}
const left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = Math.max(ans, vowel);
if (ans === k) { // 答案已经等于理论最大值
break; // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
let out = s[left];
if (out === 'a' || out === 'e' || out === 'i' || out === 'o' || out === 'u') {
vowel--;
}
}
return ans;
};
c
#define MAX(a, b) ((b) > (a) ? (b) : (a))
int maxVowels(char* s, int k) {
int ans = 0, vowel = 0;
for (int i = 0; s[i]; i++) { // 枚举窗口右端点 i
// 1. 右端点进入窗口
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
int left = i - k + 1; // 窗口左端点
if (left < 0) { // 窗口大小不足 k,尚未形成第一个窗口
continue;
}
// 2. 更新答案
ans = MAX(ans, vowel);
if (ans == k) { // 答案已经等于理论最大值
break; // 无需再循环
}
// 3. 左端点离开窗口,为下一个循环做准备
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n) ,其中 n n n 是 s s s 的长度。
- 空间复杂度: O ( 1 ) O(1) O(1) 。