LeetCode 1456:定长子串中元音的最大数目

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) ⭐⭐⭐

核心要点:

  1. 滑动窗口是本题的最优解法,充分利用了窗口移动的特性
  2. 判断元音可以使用 set 存储,也可以直接字符比较(代码中两种方式都有体现)
  3. 注意边界条件的处理
相关推荐
倒头就睡的小比特2 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
猎头南楼2 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
旖旎夜光2 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark2 天前
大规模并行计算中的负载均衡算法研究4
算法
Because_of_Her12 天前
并查集-听课笔记
笔记·算法·并查集
码流子2 天前
高速公路安全监测实践:碰撞监测预警+物联网底座,从感知到处置的闭环
大数据·人工智能·物联网·算法·架构
another heaven2 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
wzdark2 天前
从算法设计模式看编程思维的抽象能力4
算法
2601_962218612 天前
万象生鲜系统称重自动多退少补算法解决生鲜非标品痛点
大数据·数据库·人工智能·python·算法