LeetCode 1456. 定长子串中元音的最大数目 | 滑动窗口超详细解析

目录

  1. 问题描述

  2. 什么是滑动窗口?

  3. 为什么选择滑动窗口?

  4. 分步思路解析

  5. 完整代码实现与注释

  6. 复杂度分析

  7. 思考题与答案


1. 问题描述

给定一个字符串 s 和一个整数 k,要求找到所有长度为 k 的连续子字符串中,包含元音字母(a, e, i, o, u)的最大数量。例如:

  • 输入:s = "abciiidef", k = 3,输出:3(子字符串 "iii" 包含 3 个元音)。

  • 输入:s = "rhythms", k = 4,输出:0(字符串中没有元音)。


2. 什么是滑动窗口?

滑动窗口是一种高效的算法技巧,用于处理数组/字符串的子区间问题。它的核心思想是:

  • 维护一个固定或可变大小的“窗口”,通过移动窗口的左边界右边界,避免重复计算。

  • 适用于需要遍历所有连续子区间的问题,如“最大子数组和”“最长无重复子串”等。

类比理解
想象你在看一个长度为 k 的火车车厢,每次只能看到连续的 k 节车厢。当火车前进时,你只需要关注新进入视野的车厢离开视野的车厢,而不是重新数一遍所有车厢。


3. 为什么选择滑动窗口?

假设用暴力解法(遍历所有可能的子字符串),时间复杂度为 O(nk)(n 是字符串长度)。例如,当 n = 10^5k = 10^3 时,计算量会非常大。
滑动窗口将时间复杂度优化到 O(n),因为它只需遍历一次字符串,每次窗口移动仅进行常数次操作。


4. 分步思路解析

步骤 1:初始化第一个窗口

  • 计算字符串前 k 个字符中的元音数量,作为初始值。

  • 例如,s = "abciiidef", k = 3,初始窗口 "abc" 包含元音 a,数量为 1。

步骤 2:滑动窗口

  • 窗口右移:每次将窗口右边界向右移动一位(即 i 从 k 开始遍历到末尾)。

  • 更新元音数量

    • 添加新字符:如果新进入窗口的字符是元音,计数 count++

    • 移除旧字符:如果离开窗口的字符是元音,计数 count--

  • 记录最大值:每次更新后,比较当前计数与历史最大值。

关键点

  • 窗口的左右边界始终相差 k,因此只需维护一个变量 count

  • 通过“一增一减”避免重复遍历窗口内的字符。(重点!!!)


5. 完整代码实现与注释

cpp

#include 
#include 
using namespace std;

class Solution {
public:
    // 辅助函数:判断字符是否是元音字母
    bool isVowel(char c) {
        return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
    }

    int maxVowels(string s, int k) {
        int count = 0; // 当前窗口内的元音数量
        
        // 初始化第一个窗口(前k个字符)
        for (int i = 0; i < k; i++) {
            if (isVowel(s[i])) count++;
        }
        int ans = count; // 记录最大值
        
        // 滑动窗口:窗口范围为 [i-k, i-1] → [i-k+1, i]
        for (int i = k; i < s.size(); i++) {
            // 添加新进入窗口的字符(右边界)
            if (isVowel(s[i])) count++;
            // 移除离开窗口的字符(左边界)
            if (isVowel(s[i - k])) count--;
            // 更新最大值
            ans = max(ans, count);
        }
        return ans;
    }
};

代码解释

  1. 辅助函数 isVowel
    用于快速判断字符是否为元音,直接通过逻辑判断实现,时间复杂度 O(1)。

  2. 初始化窗口

    • 遍历前 k 个字符,统计初始窗口内的元音数量。

    • 例如,k = 3 时,窗口覆盖索引 0, 1, 2

  3. 滑动窗口过程

    • 循环变量 i:表示窗口的右边界,初始值为 k

    • 添加新字符s[i] 进入窗口,如果是元音则 count++

    • 移除旧字符s[i-k] 离开窗口,如果是元音则 count--

    • 更新最大值:每一步都确保 ans 保存当前最大值。


6. 复杂度分析

  • 时间复杂度:O(n),仅需遍历字符串一次。

  • 空间复杂度:O(1),仅使用常数级变量(countans)。


7. 思考题与答案

思考题 1:如果题目要求找最长子串,使得元音数量≥某个值,该如何修改代码?

答案思路
可以使用滑动窗口的变种——可变大小窗口。当窗口内元音数量不足时,扩展右边界;否则收缩左边界,记录窗口最大长度。例如:

cpp

int longestSubstring(string s, int minVowels) {
    int left = 0, maxLen = 0, count = 0;
    for (int right = 0; right < s.size(); right++) {
        if (isVowel(s[right])) count++;
        while (count >= minVowels) {
            maxLen = max(maxLen, right - left + 1);
            if (isVowel(s[left])) count--;
            left++;
        }
    }
    return maxLen;
}

思考题 2:如果字符串中包含大写字母,如何调整代码?

答案
在判断元音时,先将字符转换为小写:

cpp

bool isVowel(char c) {
    c = tolower(c);
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

总结

滑动窗口是解决子串/子数组问题的利器,通过动态维护窗口边界和状态,避免重复计算。理解其核心思想后,可以轻松应对各类变种题目。

欢迎指出不足之处!!!

你可能感兴趣的:(算法-滑动窗口(C++实现),算法)