统计字符串中的元音子字符串

标签: 哈希表 字符串

难度: Easy

子字符串 是字符串中的一个连续(非空)的字符序列。

元音子字符串 由元音('a''e''i''o''u')组成的一个子字符串,且必须包含 全部五种 元音。

给你一个字符串 word ,统计并返回 word元音子字符串的数目

示例 1:

输入:word = "aeiouu"
输出:2
解释:下面列出 word 中的元音子字符串(斜体加粗部分):
- "aeiouu"
- "aeiouu"

示例 2:

输入:word = "unicornarihan"
输出:0
解释:word 中不含 5 种元音,所以也不会存在元音子字符串。

示例 3:

输入:word = "cuaieuouac"
输出:7
解释:下面列出 word 中的元音子字符串(斜体加粗部分):
- "cuaieuouac"
- "cuaieuouac"
- "cuaieuouac"
- "cuaieuouac"
- "cuaieuouac"
- "cuaieuouac"
- "cuaieuouac"

示例 4:

输入:word = "bbaeixoubb"
输出:0
解释:所有包含全部五种元音的子字符串都含有辅音,所以不存在元音子字符串。

提示:

  • 1 <= word.length <= 100
  • word 仅由小写英文字母组成

Submission

运行时间: 36 ms

内存: 0.0 MB

class Solution:
    def countVowelSubstrings(self, word: str) -> int:
        counter = Counter()
        n = len(word)
        
        start = left = right = 0
        vowels = 'aeiou'
        res = 0
        
        while right < n:
            if start == right:
                while start < n:
                    if word[start] in vowels:
                        break
                    start += 1
                
                left = start
                right = start
            
            while right < n:
                if word[right] not in vowels:
                    self.clear(counter)
                    break
                counter[word[right]] += 1
                right += 1
                if self.all_vowels(counter):
                    break
                    
                
            if self.all_vowels(counter):
                while left < right:
                    counter[word[left]] -= 1
                    left += 1
                    if not self.all_vowels(counter):
                        break
                        
                res += left - start
                left -= 1
                counter[word[left]] += 1
                
            else:
                start = right
                
        return res
                
        
        
    def all_vowels(self, counter):
        for c in 'aeiou':
            if counter[c] == 0:
                return False
            
        return True
    
    def clear(self, counter):
        for c in 'aeiou':
            counter[c] = 0

Explain

此题解采用了滑动窗口的策略来寻找包含所有五个元音至少各一次的最长子字符串。首先,我们使用三个指针 start, left 和 right 来定义当前探索的子字符串。我们从左到右遍历字符串,使用一个计数器来统计各个元音字母的出现次数。当我们遇到一个非元音字符时,我们将清空计数器并将 start 移动到 right 的位置。如果在 right 指针的位置我们发现所有五个元音都至少出现一次,我们就尝试从 left 开始减少窗口的大小直到不满足条件为止。这个过程中,我们计算从 start 到 left-1 所有可能的元音子字符串的数量并累加到结果中。每找到一个有效的子字符串后,我们调整 left 指针并尝试寻找新的子字符串。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def countVowelSubstrings(self, word: str) -> int:
        counter = Counter()  # 创建计数器用于统计元音
        n = len(word)
        start = left = right = 0  # 初始化三个指针
        vowels = 'aeiou'  # 元音字符集
        res = 0  # 结果计数器
        while right < n:
            if start == right:  # 寻找首个元音字符作为起点
                while start < n:
                    if word[start] in vowels:
                        break
                    start += 1
                left = start
                right = start
            while right < n:  # 扩展 right 指针以包含所有元音
                if word[right] not in vowels:
                    self.clear(counter)
                    break
                counter[word[right]] += 1
                right += 1
                if self.all_vowels(counter):  # 检查是否包含所有元音
                    break
            if self.all_vowels(counter):  # 确认找到有效子字符串后,尝试减少窗口大小
                while left < right:
                    counter[word[left]] -= 1
                    left += 1
                    if not self.all_vowels(counter):
                        break
                res += left - start  # 计算子字符串数量
                left -= 1
                counter[word[left]] += 1  # 调整计数器
            else:
                start = right  # 非元音字符,重置窗口
        return res
    def all_vowels(self, counter):
        for c in 'aeiou':
            if counter[c] == 0:  # 检查所有元音是否都至少出现一次
                return False
        return True
    def clear(self, counter):
        for c in 'aeiou':  # 清空计数器
            counter[c] = 0

Explore

使用 start, left, 和 right 三个指针作为滑动窗口的策略可以有效减少不必要的计算。这种方法允许我们动态地调整窗口的大小来寻找包含所有五个元音至少一次的子字符串。如果从头开始检查每个子字符串,则每次都需要重新计算元音的出现次数,这会导致大量重复的计算,尤其是在字符串较长时。滑动窗口策略可以在扩展和缩小窗口时,通过简单的计数器调整来更新元音的计数,大大提高了效率。

处理非元音字符时,清空计数器并重置 start 的位置是必要的,因为题目要求的是找到仅包含元音字母的子字符串。遇到非元音字符意味着当前子字符串不再满足条件,因此必须重新开始搜索新的潜在子字符串。这种处理方式不会漏掉有效子字符串,因为一旦包含非元音字符,子字符串就不再符合题目要求。

在缩小窗口的过程中,当 `left` 指针移动导致某个元音的数量减少到零,即不满足所有元音至少出现一次的条件时,我们需要回退 `left` 指针一步并调整计数器,是为了保证在下一次循环中,窗口仍然是有效的起始点。这样,我们可以继续从这个点开始尝试寻找新的有效子字符串。这一步骤是确保算法正确性的关键,它使得每个可能的起始点都能被正确计算。

算法通过动态地调整 `start` 和 `left` 指针来确保每个元音子字符串只被计算一次。在找到一个包含所有元音至少一次的子字符串后,我们通过逐步移动 `left` 指针并减少窗口大小直到不再满足条件,这一过程中计算的是从 `start` 到 `left-1` 的所有可能子字符串数量。随后,通过适当调整 `start` 和 `left` 指针来开始新的搜索,避免了重复计算。