LeetCode 热题 HOT 100(滑动窗口 438)关联题 1456. 定长子串中元音的最大数目

48 阅读3分钟

题目描述

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

中等

提示

给你字符串 s 和整数 k 。

请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。

英文中的 元音字母 为(aeiou)。

 

示例 1:

输入: s = "abciiidef", k = 3
输出: 3
解释: 子字符串 "iii" 包含 3 个元音字母。

示例 2:

输入: s = "aeiou", k = 2
输出: 2
解释: 任意长度为 2 的子字符串都包含 2 个元音字母。

示例 3:

输入: s = "leetcode", k = 3
输出: 2
解释: "lee""eet""ode" 都包含 2 个元音字母。

示例 4:

输入: s = "rhythms", k = 4
输出: 0
解释: 字符串 s 中不含任何元音字母。

示例 5:

输入: s = "tryhard", k = 4
输出: 1

 

提示:

  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成
  • 1 <= k <= s.length

思路

我们要计算所有长度恰好为 k 的子串中,最多可以包含多少个元音字母。

假如我们已经计算出了子串 abc 的元音个数,那么从子串 abc 到子串 bci,只需要考虑移除(离开窗口)的字母 a 是不是元音,以及添加(进入窗口)的字母 i 是不是元音即可,因为中间的字母 b 和 c 都在这两个子串中。

举例

示例 1,s=abciiidef, k=3。

从左到右遍历 s。

首先统计前 k−1=2 个字母的元音个数,这有 1 个。

s[2]=c 进入窗口,此时找到了第一个长为 k 的子串 abc,现在元音个数有 1 个,更新答案最大值。然后 s[0]=a 离开窗口,现在元音个数有 0 个。

s[3]=i 进入窗口,此时找到了第二个长为 k 的子串 bci,现在元音个数有 1 个,更新答案最大值。然后 s[1]=b 离开窗口,现在元音个数有 1 个。

s[4]=i 进入窗口,此时找到了第三个长为 k 的子串 cii,现在元音个数有 2 个,更新答案最大值。然后 s[2]=c 离开窗口,现在元音个数有 2 个。

s[5]=i 进入窗口,此时找到了第四个长为 k 的子串 iii,现在元音个数有 3 个,更新答案最大值。然后 s[3]=i 离开窗口,现在元音个数有 2 个。

s[6]=d 进入窗口,此时找到了第五个长为 k 的子串 iid,现在元音个数有 2 个,更新答案最大值。然后 s[4]=i 离开窗口,现在元音个数有 1 个。

s[7]=e 进入窗口,此时找到了第六个长为 k 的子串 ide,现在元音个数有 2 个,更新答案最大值。然后 s[5]=i 离开窗口,现在元音个数有 1 个。

s[8]=f 进入窗口,此时找到了第七个长为 k 的子串 def,现在元音个数有 1 个,更新答案最大值。遍历结束。

定长滑窗套路

总结成三步:入-更新-出。

入:下标为 i 的元素进入窗口,更新相关统计量。如果 i<k−1 则重复第一步。

更新:更新答案。一般是更新最大值/最小值。

出:下标为 i−k+1 的元素离开窗口,更新相关统计量。

以上三步适用于所有定长滑窗题目。

const AllVowels = "aeiou"

func maxVowels(s string, k int) (ans int) {
    vowel := 0
    for i := 0; i < len(s); i++ {
        if IsVowels(s[i]) {
            vowel++
        }
        if i-k+1 < 0 {
            continue
        }
        ans = max(ans, vowel)
        if out := s[i-k+1]; IsVowels(out) {
            vowel--
        }
    }

    return ans
}

func IsVowels(ch byte) bool {
    for i := 0; i < len(AllVowels); i++ {
        if ch == AllVowels[i] {
            return true
        }
    }
    return false
}