LeetCode 1456:定长子串中元音的最大数目
-
- [1. 题目介绍](#1. 题目介绍)
- [2. 解题思路](#2. 解题思路)
- [3. 示例代码](#3. 示例代码)
-
- [3.1 解法一:暴力枚举](#3.1 解法一:暴力枚举)
- [3.2 解法二:滑动窗口](#3.2 解法二:滑动窗口)
- [4. 拓展:滑动窗口可视化页面](#4. 拓展:滑动窗口可视化页面)
- [5. 总结](#5. 总结)

🎬 博主名称: 超级苦力怕
🔥 个人专栏: 《LeetCode 题解》
🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!
本篇文章给大家带来的是 LeetCode 第 1456 题------定长子串中元音的最大数目 。这是一道 滑动窗口的经典入门题 ,虽然题目以元音计数为背景,但核心思想「固定长度窗口的滑动」是所有滑动窗口问题的通用解法,掌握后可以轻松解决类似问题。
本文将采用 Java 语言进行讲解,从暴力解法出发,逐步优化到滑动窗口,帮助大家深入理解滑动窗口的核心原理。话不多说,让我们开始吧!
1. 题目介绍
1456. 定长子串中元音的最大数目
直达链接:LeetCode 1456
给你字符串 s 和整数 k。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的元音字母为:a, e, i, o, u。

2. 解题思路
这道题要求在字符串 s 中找到长度为 k 的子串中,元音字母数量最多的那一个。关键在于理解题意:我们需要遍历所有长度为 k 的子串,统计每个子串中的元音个数,然后返回最大值。
解法一:暴力枚举
算法思想:
- 枚举所有长度为
k的子串 - 对每个子串遍历统计元音字母个数
- 取所有子串中元音数量的最大值
复杂度分析:
- 时间复杂度:O(n * k),需要遍历每个子串的每个字符
- 空间复杂度:O(1)
解法二:滑动窗口(推荐)
通过假设一个长度为k,可以移动的窗口,这样每一次移动窗口的时候,我们只需要考虑最右边和最左边的字符。
实现思路 :计算出第一个窗口abc 的元音个数,那么从子串abc移动到bci ,我们只需要考虑离开窗口的字母a是不是元音,以及进入窗口的字母i是不是元音即可,其他字母都在窗口内,因此不需要判断。
核心代码:文字描述(以示例1为例:s = "abciiidef", k = 3)
以示例1为例:s = "abciiidef", k = 3
这里的 vowel 代指当前窗口所含元音字母
1. 初始化窗口 [0,2] -> "abc"
- 此时已记录 'abc',vowel = 1
2. 窗口向右滑动到 [1,3] -> "bci"
- 进入窗口: 'i'(是元音) -> vowel = 2
- 离开窗口: 'a'(是元音) -> vowel = 1
- 更新答案: maxCount = max(1, 2) = 2
3. 窗口向右滑动到 [2,4] -> "cii"
- 进入窗口: 'i'(是元音) -> vowel = 2
- 离开窗口: 'b'(否) -> vowel = 2
4. 窗口向右滑动到 [3,5] -> "iii"
- 进入窗口: 'i'(是元音) -> vowel = 3
- 离开窗口: 'c'(否) -> vowel = 3
- 更新答案: maxCount = max(2, 3) = 3
最终答案: maxCount = 3
复杂度分析
- 时间复杂度:O(n),每个字符最多被访问两次
- 空间复杂度:O(1),只使用常数额外空间
- 相比暴力枚举(需要重复计算窗口内字符),滑动窗口利用了窗口移动的规律,避免了重复计算
3. 示例代码
3.1 解法一:暴力枚举
Java 实现:
java
class Solution {
public int maxVowels(String s, int k) {
// 使用 HashSet 存储元音字母,方便 O(1) 时间复杂度查找
Set<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u'));
// 用于记录所有子串中元音数量的最大值
int maxCount = 0;
// 枚举所有长度为 k 的子串的起始位置 i
// 起始位置范围:0 到 s.length() - k
for (int i = 0; i <= s.length() - k; i++) {
// 统计当前子串 s[i, i+k) 中的元音数量
int count = 0;
// 遍历当前窗口内的所有字符
for (int j = i; j < i + k; j++) {
// 如果当前字符是元音,则计数加一
if (vowels.contains(s.charAt(j))) {
count++;
}
}
// 更新最大元音数量
maxCount = Math.max(maxCount, count);
}
// 返回最大元音数量
return maxCount;
}
}
3.2 解法二:滑动窗口
核心思想:维护一个长度为 k 的滑动窗口,通过不断向右移动窗口,利用上一次窗口的结果快速计算新窗口的元音数量,避免重复计算。
java
class Solution {
public int maxVowels(String S, int k) {
// 将字符串转换为字符数组,提高访问效率
char[] s = S.toCharArray();
// ans:记录遍历过程中遇到的最大元音数量
int ans = 0;
// vowel:记录当前窗口内的元音数量
int vowel = 0;
// 遍历字符串,枚举每个可能成为窗口右端点的位置 i
// i 的取值范围:0 到 s.length - 1
for (int i = 0; i < s.length; i++) {
// ===== 步骤 1:将当前右端点字符加入窗口 =====
// 判断当前字符是否是元音,如果是则元音计数加一
if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' || s[i] == 'o' || s[i] == 'u') {
vowel++;
}
// 计算当前窗口的左端点位置
// 当 i = k-1 时,left = 0,此时窗口大小正好为 k
int left = i - k + 1;
// 如果左端点位置小于 0,说明窗口大小还不足 k
// 尚未形成第一个完整的窗口,继续下一轮循环
if (left < 0) {
continue;
}
// ===== 步骤 2:更新答案 =====
// 当窗口大小达到 k 时,比较并更新最大元音数量
ans = Math.max(ans, vowel);
// ===== 步骤 3:将当前左端点字符移出窗口 =====
// 为下一轮循环做准备,下一轮窗口会向右移动一位
// 记录即将离开窗口的字符
char out = s[left];
// 如果离开的字符是元音,则元音计数减一
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
// 返回所有窗口中元音数量的最大值
return ans;
}
}
优化思路:窗口的最大长度为 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++) {
// ===== 步骤 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) {
continue;
}
// ===== 步骤 2:更新答案 =====
// 当窗口大小达到 k 时,比较并更新最大元音数量
ans = Math.max(ans, vowel);
// ===== 优化:提前返回 =====
// 如果当前答案已经等于窗口长度 k,说明已达到理论最大值
// 不可能找到比 k 更大的元音数量了,可以直接返回
if (ans == k) {
return k;
}
// ===== 步骤 3:将当前左端点字符移出窗口 =====
char out = s[left];
if (out == 'a' || out == 'e' || out == 'i' || out == 'o' || out == 'u') {
vowel--;
}
}
return ans;
}
}
4. 拓展:滑动窗口可视化页面
视频播放页面下载地址:github仓库,如果打不开请选择:gitee仓库,在后续会不断更新使用到的HTML页面。

5. 总结
| 解法 | 时间复杂度 | 空间复杂度 | 推荐指数 |
|---|---|---|---|
| 滑动窗口 | O(n) | O(1) | ⭐⭐⭐⭐⭐ |
| 暴力枚举 | O(n * k) | O(1) | ⭐⭐⭐ |
核心要点:
- 滑动窗口是本题的最优解法,充分利用了窗口移动的特性
- 判断元音可以使用
set存储,也可以直接字符比较(代码中两种方式都有体现) - 注意边界条件的处理
