今日题目

简介

遇到与给定对象的连续子区间有关的问题时,一个很容易联想到的技巧就是滑动窗口。

滑窗的思路非常简单,就是维护一个窗口,不断滑动,然后更新答案。

但滑窗的难点不是算法的思路,而是各种细节问题。比如如何向窗口中添加新元素,如何缩小窗口,在窗口滑动的哪个阶段更新结果。

以下这套滑动窗口算法的代码框架,标记了实现中需要注意的细节,和输出 debug 的位置。以后遇到相关的问题,只需要默写出该框架,然后改三个地方就行:

/* 滑动窗口算法框架 */
const slidingWindow = (s) => {
    const win = [];
    
    let left = 0, right = 0;
    while (right < s.length) {
        // c 是将移入窗口的字符
        const c = s[right];
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        ...

        /*** debug 输出的位置 ***/
        console.log('window:', win);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (win needs shrink) {
            // d 是将移出窗口的字符
            const d = s[left];
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            ...
        }
    }
}

其中两处 ... 表示的更新窗口数据的地方,直接往里面填逻辑就行。而且这两处 ... 的操作分别是扩大和缩小窗口的更新操作,它们的操作是完全对称的。

另外,虽然滑动窗口代码框架中有一个嵌套的 while 循环,但算法的时间复杂度依然是 O(N) ,其中 N 是输入字符串/数组的长度。因为字符串(或数组)中的每个元素都只会进入窗口一次,然后被移出窗口一次,不会有某些元素多次进入和离开窗口的情况。所以算法的时间复杂度就和字符串(或数组)的长度成正比。

小抄

我写了首诗,把滑动窗口算法算法变成了默写题