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) 。
相关推荐
liuyao_xianhui2 小时前
优选算法_模拟_提莫攻击_C++
开发语言·c++·算法·动态规划·哈希算法·散列表
玛丽莲茼蒿2 小时前
LeetCode hot100【相交链表】【简单】
算法·leetcode·职场和发展
罗湖老棍子2 小时前
They Are Everywhere(Codeforces- P701C)
算法·滑动窗口·codeforce题解
wen__xvn2 小时前
力扣模拟题刷题
算法·leetcode
bbbb3652 小时前
算法复杂度与能耗关系的多变量分析研究的技术7
算法
不要秃头的小孩2 小时前
力扣刷题——111.二叉树的最小深度
数据结构·python·算法·leetcode
wutang0ka2 小时前
LeeCode HOT 100 104.二叉树的最大深度
算法
散峰而望2 小时前
【基础算法】从入门到实战:递归型枚举与回溯剪枝,暴力搜索的初级优化指南
数据结构·c++·后端·算法·机器学习·github·剪枝
setmoon2142 小时前
C++代码规范化工具
开发语言·c++·算法