Medium

题目描述

给你一个字符串 word 和一个非负整数 k

返回 word 的子字符串中包含每个元音字母(‘a’、’e’、‘i’、‘o’ 和 ‘u’)至少一次且恰好包含 k 个辅音字母的子字符串总数。

示例 1:

输入:word = "aeioqq", k = 1
输出:0
解释:没有包含每个元音字母的子字符串。

示例 2:

输入:word = "aeiou", k = 0
输出:1
解释:唯一包含每个元音字母且零个辅音字母的子字符串是 word[0..4],即 "aeiou"。

示例 3:

输入:word = "ieaouqqieaouqq", k = 1
输出:3
解释:包含每个元音字母和一个辅音字母的子字符串是:
- word[0..5],即 "ieaouq"
- word[6..11],即 "qieaou"  
- word[7..12],即 "ieaouq"

约束条件:

  • 5 <= word.length <= 2 * 10^5
  • word 仅由小写英文字母组成
  • 0 <= k <= word.length - 5

解题思路

这道题要求找到包含所有5个元音字母且恰好包含k个辅音字母的子字符串数量。我们可以使用滑动窗口结合二分查找的方法来解决。

核心思路:

  1. 对于每个右端点r,我们需要找到满足条件的左端点范围
  2. 满足条件的子字符串需要:包含所有5个元音字母 + 恰好k个辅音字母
  3. 可以转化为:恰好k个辅音的数量 - 恰好k-1个辅音的数量

具体实现:

  • 定义辅助函数atMostK(word, k),计算最多包含k个辅音且包含所有元音的子字符串数量
  • 使用滑动窗口维护当前窗口内的元音和辅音数量
  • 当窗口满足"包含所有元音且辅音数量≤k"时,所有以当前右端点结尾、左端点在[left, r]范围内的子字符串都满足条件
  • 最终答案 = atMostK(word, k) - atMostK(word, k-1)

时间复杂度分析:

  • 每次调用atMostK的时间复杂度为O(n)
  • 总时间复杂度为O(n),其中n是字符串长度

代码实现

class Solution {
public:
    long long countOfSubstrings(string word, int k) {
        return atMostK(word, k) - atMostK(word, k - 1);
    }
    
private:
    long long atMostK(string& word, int k) {
        if (k < 0) return 0;
        
        int n = word.length();
        int vowelCount[5] = {0}; // a, e, i, o, u
        int consonants = 0;
        int left = 0;
        long long result = 0;
        
        for (int right = 0; right < n; right++) {
            char c = word[right];
            if (isVowel(c)) {
                vowelCount[getVowelIndex(c)]++;
            } else {
                consonants++;
            }
            
            while (consonants > k) {
                char leftChar = word[left];
                if (isVowel(leftChar)) {
                    vowelCount[getVowelIndex(leftChar)]--;
                } else {
                    consonants--;
                }
                left++;
            }
            
            while (hasAllVowels(vowelCount) && consonants <= k) {
                result += right - left + 1;
                char leftChar = word[left];
                if (isVowel(leftChar)) {
                    vowelCount[getVowelIndex(leftChar)]--;
                } else {
                    consonants--;
                }
                left++;
            }
        }
        
        return result;
    }
    
    bool isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
    
    int getVowelIndex(char c) {
        switch(c) {
            case 'a': return 0;
            case 'e': return 1;
            case 'i': return 2;
            case 'o': return 3;
            case 'u': return 4;
            default: return -1;
        }
    }
    
    bool hasAllVowels(int vowelCount[]) {
        for (int i = 0; i < 5; i++) {
            if (vowelCount[i] == 0) return false;
        }
        return true;
    }
};
class Solution:
    def countOfSubstrings(self, word: str, k: int) -> int:
        def atMostK(k):
            if k < 0:
                return 0
            
            vowel_count = {'a': 0, 'e': 0, 'i': 0, 'o': 0, 'u': 0}
            consonants = 0
            left = 0
            result = 0
            
            for right in range(len(word)):
                c = word[right]
                if c in vowel_count:
                    vowel_count[c] += 1
                else:
                    consonants += 1
                
                while consonants > k:
                    left_char = word[left]
                    if left_char in vowel_count:
                        vowel_count[left_char] -= 1
                    else:
                        consonants -= 1
                    left += 1
                
                while all(count > 0 for count in vowel_count.values()) and consonants <= k:
                    result += right - left + 1
                    left_char = word[left]
                    if left_char in vowel_count:
                        vowel_count[left_char] -= 1
                    else:
                        consonants -= 1
                    left += 1
            
            return result
        
        return atMostK(k) - atMostK(k - 1)
public class Solution {
    public long CountOfSubstrings(string word, int k) {
        return AtMostK(word, k) - AtMostK(word, k - 1);
    }
    
    private long AtMostK(string word, int k) {
        if (k < 0) return 0;
        
        int n = word.Length;
        int[] vowelCount = new int[5]; // a, e, i, o, u
        int consonants = 0;
        int left = 0;
        long result = 0;
        
        for (int right = 0; right < n; right++) {
            char c = word[right];
            if (IsVowel(c)) {
                vowelCount[GetVowelIndex(c)]++;
            } else {
                consonants++;
            }
            
            while (consonants > k) {
                char leftChar = word[left];
                if (IsVowel(leftChar)) {
                    vowelCount[GetVowelIndex(leftChar)]--;
                } else {
                    consonants--;
                }
                left++;
            }
            
            while (HasAllVowels(vowelCount) && consonants <= k) {
                result += right - left + 1;
                char leftChar = word[left];
                if (IsVowel(leftChar)) {
                    vowelCount[GetVowelIndex(leftChar)]--;
                } else {
                    consonants--;
                }
                left++;
            }
        }
        
        return result;
    }
    
    private bool IsVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }
    
    private int GetVowelIndex(char c) {
        return c switch {
            'a' => 0,
            'e' => 1,
            'i' => 2,
            'o' => 3,
            'u' => 4,
            _ => -1
        };
    }
    
    private bool HasAllVowels(int[] vowelCount) {
        for (int i = 0; i < 5; i++) {
            if (vowelCount[i] == 0) return false;
        }
        return true;
    }
}
var countOfSubstrings = function(word, k) {
    const atMostK = (k) => {
        if (k < 0) return 0;
        
        const vowelCount = {a: 0, e: 0, i: 0, o: 0, u: 0};
        let consonants = 0;
        let left = 0;
        let result = 0;
        
        for (let right = 0; right < word.length; right++) {
            const c = word[right];
            if (c in vowelCount) {
                vowelCount[c]++;
            } else {
                consonants++;
            }
            
            while (consonants > k) {
                const leftChar = word[left];
                if (leftChar in vowelCount) {
                    vowelCount[leftChar]--;
                } else {
                    consonants--;
                }
                left++;
            }
            
            while (hasAllVowels(vowelCount) && consonants <= k) {
                result += right - left + 1;
                const leftChar = word[left];
                if (leftChar in vowelCount) {
                    vowelCount[leftChar]--;
                } else {
                    consonants--;
                }
                left++;
            }
        }
        
        return result;
    };
    
    const hasAllVowels = (vowelCount) => {
        return Object.values(vowelCount).every(count => count > 0);
    };
    
    return atMostK(k) - atMostK(k - 1);
};

复杂度分析

复杂度分析
时间复杂度O(n),其中 n 是字符串长度。虽然有嵌套循环,但每个字符最多被访问常数次
空间复杂度O(1),只使用了固定大小的数组/哈希表存储元音字母计数

相关题目