LeetCode 1456. 定长子串中元音的最大数目【定长滑窗模板题】中等

本文属于「征服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^5
  • s 由小写英文字母组成
  • 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 。总结为三步:入-更新-出

  1. :下标为 i i i 的元素进入窗口,更新相关统计量 。如果窗口左端点 i − k + 1 < 0 i - k + 1 < 0 i−k+1<0 ,则尚未形成第一个窗口,重复第一步。
  2. 更新:更新答案,一般是更新最大值/最小值。
  3. :到第三步则说明窗口左端点 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) 。
相关推荐
灵感__idea6 小时前
Hello 算法:贪心的世界
前端·javascript·算法
ZK_H7 小时前
嵌入式c语言——关键字其6
c语言·开发语言·计算机网络·面试·职场和发展
澈2077 小时前
深入浅出C++滑动窗口算法:原理、实现与实战应用详解
数据结构·c++·算法
ambition202428 小时前
从暴力搜索到理论最优:一道任务调度问题的完整算法演进历程
c语言·数据结构·c++·算法·贪心算法·深度优先
cmpxr_8 小时前
【C】原码和补码以及环形坐标取模算法
c语言·开发语言·算法
qiqsevenqiqiqiqi8 小时前
前缀和差分
算法·图论
代码旅人ing8 小时前
链表算法刷题指南
数据结构·算法·链表
Yungoal8 小时前
常见 时间复杂度计算
c++·算法
6Hzlia8 小时前
【Hot 100 刷题计划】 LeetCode 48. 旋转图像 | C++ 矩阵变换题解
c++·leetcode·矩阵
不爱吃炸鸡柳9 小时前
单链表专题(完整代码版)
数据结构·算法·链表