滑动窗口集合

概述

一种非常典型的编程思想,滑动窗口。

专门针对滑动窗口的问题,框架更是一看就懂。

while (right < s.length()) {
    //移动窗口右边时机,向窗口添加元素
    w.add(s.charAt(right++));
    // 判断是否需要移动窗口坐标,移除元素
    while (w.check()) {
        w.remove(s.charAt(left++));
    }
    // debug时机,查看当前窗口,或打印窗口内容
    // 检查合法条件时机,以下为举例
    size = Math.max(size, w.size);
}

这个框架的好处是非常清晰,主要逻辑——添加元素,删除元素,检查合法性,用什么来帮助快速检查合法性都交给窗口。窗口对象需要实现这些方法。

窗口类型

固定窗口

特点是left和right一同移动。

567. Permutation in String
    1. Permutation in String
  • s2是否包含s1的全排列
  • https://leetcode.com/problems/permutation-in-string/

以这道题是我做的第一道题,将进行详细展开。如果不以滑动窗口的方式去考虑问题,那这种题会变得很麻烦。优化策略很多,也不好实现。

我一开始考虑到的解法:

for c in s2:
	if 当前子串是否等于s1的全排列之一
        记录下标

复杂度主要来源于if的判断。

解法一:暴力

全排列,只需要所有字母一致即可,暴力遍历即可求解。

  • 时间复杂度:O(N M ^ 2), O(M ^ 2)为两个for循环,在当前子串中搜索s1的所有字母
  • 空间复杂度,O(1)

解法二:排序

上面O(M^2)有待优化,既然求是否全排列之一,那么就可以对其排序,那就可以直接在O(M)比出结果

  • 时间复杂度,O(N M log M),主要是排序开销
  • 空间复杂度,O(1)

解法三:hash

能否在O(1)求解是否为全排列之一?s1不需要每次都遍历一遍。可以将s1的全排列的所有可能都放入hash table中,然后遍历,每次判断当前子串的时候只需要判断它在不在hash table中即可。

  • 时间复杂度,O(N M),很遗憾即使有hash table,每次判断在不在hash table,这个操作也是O(M)而不是hash查找的O(1),这也是常见的误区之一,核心原因是因为hash函数的求取直接和s1长度M挂钩。
  • 空间复杂度,O(M!),指数级复杂度,等于s1的全排列。

看到了吧,以上种种方式都似乎指出该问题不简单,不管是复杂度还是实现上。但该问题其实有时间上O(1)空间上O(M)的高效解法。这就是滑动窗口的强大。

解法四:滑动窗口

滑动窗口的优化核心是窗口滑动新增一个元素(删除一个元素),这意味着不需要完全遍历,有O(1)解决问题的潜力,只要能很好利用旧窗口的知识。

所以,实现窗口也就是如何利用已有信息,更新新的信息。

对于本题,窗口利用hash桶,记录出现过的字符。新增元素则加一,移除元素则减一。若所有桶都为0,则表示当前字符串和s1的字符完全一致。

/*
全新滑动窗口框架
Runtime: 9 ms, faster than 52.34% of Java online submissions for Permutation in String.
Memory Usage: 40.2 MB, less than 8.87% of Java online submissions for Permutation in String.
* */
public class SolutionV2 {
    public boolean checkInclusion(String s1, String s2) {
        int l,r;
        l = r = 0;
        Window w = new Window(s1);
        while (r < s2.length()) {
            w.add(s2.charAt(r++));
            if (r > w.size) {
                w.remove(s2.charAt(l++));
            }
            // System.out.println(l+ " " + r);
            // w.disp();
            if (w.check()) return true;
        }

        return false;
    }

    // public static void main(String[] args) {
    //     System.out.println(new SolutionV2().checkInclusion(
    //             "a",
    //             "a"));
    // }

    class Window {

        int[] map;

        int size, count;

        public Window(String target) {
            map = new int[130];
            this.size = target.length();
            for (int i = 0; i < size; ++i) {
                if (map[target.charAt(i)]++ == 0) ++count;
            }
        }

        public void add(char c) {
            if (--map[c] == 0) --count;
        }

        public void remove(char c) {
            if (map[c]++ == 0) ++count;
        }

        public boolean check() {
            if (count == 0) return true;
            return false;
        }

        public void disp() {
            for (int i = 'A'; i <= 'z'; i++) {
                System.out.print(map[i] + " ");
            }
            System.out.println();
        }
    }
}
438. Find All Anagrams in a String
    1. Find All Anagrams in a String
  • 寻找所有异构字串起始下标
  • https://leetcode.com/problems/find-all-anagrams-in-a-string/

和上一道题基本完全一致。

package com.leetcode.datastructure.array.window.q438;

import java.util.ArrayList;
import java.util.List;

/*
 * 438. Find All Anagrams in a String
 * 寻找所有异构字串起始下标
 * https://leetcode.com/problems/find-all-anagrams-in-a-string/
 * */

/*
滑动窗口
时间复杂度,O(N)
滑动遍历N个字符,由于hash,异构字符串判断为O(1)
空间复杂度,O(K),K为不同字符数
Runtime: 7 ms, faster than 76.50% of Java online submissions for Find All Anagrams in a String.
        Memory Usage: 39.5 MB, less than 98.83% of Java online submissions for Find All Anagrams in a String.
*/

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int left, right;
        Window w = new Window(p);
        List<Integer> res = new ArrayList<>();
        left = right = 0;
        while (right < s.length()) {
            w.add(s.charAt(right++));
            if (right > w.size) {
                w.remove(s.charAt(left++));
            }
            // w.disp();
            if (w.check()) res.add(left);
            // System.out.println(left + " " + right );
        }
        return res;
    }

    // public static void main(String[] args) {
    //     System.out.println(new Solution().findAnagrams("cbaebabacd",
    //             "abc"));
    // }

    class Window {

        int[] map;

        int size, count;

        public Window(String target) {
            map = new int[130];
            this.size = target.length();
            for (int i = 0; i < size; ++i) {
                if (map[target.charAt(i)]++ == 0) ++count;
            }
        }

        public void add(char c) {
            if (--map[c] == 0) --count;
        }

        public void remove(char c) {
            if (map[c]++ == 0) ++count;
        }

        public boolean check() {
            if (count == 0) return true;
            return false;
        }

        public void disp() {
            for (int i = 'A'; i <= 'z'; i++) {
                System.out.print(map[i] + " ");
            }
            System.out.println();
        }
    }
}

不定长窗口

76. Minimum Window Substring
    1. Minimum Window Substring
  • 寻找包含目标字所有字母的最小子串
  • https://leetcode.com/problems/minimum-window-substring/

和567非常像。

版本一,想当然地遍历了所有可能的窗口大小,因此算法复杂度为O(N^2)。但实际上,该类问题属于不定长窗口,不需要遍历所有窗口。它的基本思想是:移动右边,直到符合要求,才开始移动左边。采用双指针的方式,可以在O(N)的时间内解决问题,非常经典。

/*
* 76. Minimum Window Substring
* 寻找包含目标字所有字母的最小子串
* https://leetcode.com/problems/minimum-window-substring/
*
* */

/*
算法复杂度,O(N N) + O(N M)
其中N,M分别为s,t的长度。
里层如q567,O(N) + O(M),重复N次。
空间复杂度,O(M + K)
K为桶固定常数开销。
Runtime: 871 ms, faster than 5.02% of Java online submissions for Minimum Window Substring.
Memory Usage: 39.5 MB, less than 54.77% of Java online submissions for Minimum Window Substring.
*/

class Solution {
    public String minWindow(String s, String t) {
        for (int n = t.length(); n <= s.length(); ++n) {
            Window w = new Window(t, n);
            for (int i = 0; i < s.length(); ++i) {
                if (i < w.size) {
                    w.add(s.charAt(i));
                } else {
                    w.remove(s.charAt(i - w.size));
                    w.add(s.charAt(i));
                }
                System.out.println(s.charAt(i));
                w.disp();
                if (w.check()) return s.substring(i-w.size+1,i+1);
            }
            System.out.println("=============");
        }
        return "";
    }

    public static void main(String[] args) {
        System.out.println(new Solution().minWindow("a"
                , "a"));
    }
}

class Window {

    int[] map;

    int size, count;

    public Window(String target, int size) {
        map = new int[130];
        this.size = size;
        for (int i = 0; i < target.length(); ++i) {
            if (map[target.charAt(i)]++ == 0) ++count;
        }
    }

    public void add(char c) {
        if (--map[c] == 0) --count;
    }

    public void remove(char c) {
        if (map[c]++ == 0) ++count;
    }

    public boolean check() {
        if (count == 0) return true;
        return false;
    }

    public void disp() {
        for (int i = 'A'; i <= 'z'; i++) {
            System.out.print(map[i]+" ");
        }
        System.out.println();
    }
}

版本二:

/*
全新框架,单边收缩版本
Runtime: 4 ms, faster than 83.81% of Java online submissions for Minimum Window Substring.
Memory Usage: 40.9 MB, less than 13.46% of Java online submissions for Minimum Window Substring.
*/
public class SolutionV2 {

    public String minWindow(String s, String t) {
        int left, right, rl, rr;
        Window w = new Window(t);
        left = right = 0;
        rl = 0;
        rr = Integer.MAX_VALUE;
        // 移动右边
        while (right < s.length()) {
            w.add(s.charAt(right++));
            // 收缩左边
            if (w.check()) {
                while (w.check()) {
                    w.remove(s.charAt(left++));
                    // System.out.println(left + " " + right);
                }
                if (rr - rl > right - left + 1) {
                    rr = right;
                    rl = left - 1;
                }
            }

        }

        return rr == Integer.MAX_VALUE ? "" : s.substring(rl, rr);
    }

    // public static void main(String[] args) {
    //     System.out.println(new SolutionV2().minWindow("ADOBECODEBANC"
    //             , "ABC"));
    // }

    class Window {

        int[] map;

        int size, count;

        public Window(String target) {
            map = new int[130];
            this.size = target.length();
            for (int i = 0; i < size; ++i) {
                if (map[target.charAt(i)]++ == 0) ++count;
            }
        }

        public void add(char c) {
            if (--map[c] == 0) --count;
        }

        public void remove(char c) {
            if (map[c]++ == 0) ++count;
        }

        public boolean check() {
            if (count == 0) return true;
            return false;
        }

        public void disp() {
            for (int i = 'A'; i <= 'z'; i++) {
                System.out.print(map[i] + " ");
            }
            System.out.println();
        }
    }
}

3. Longest Substring Without Repeating Characters
    1. Longest Substring Without Repeating Characters
  • 不重复的最长字串
  • https://leetcode.com/problems/longest-substring-without-repeating-characters/

这个问题和问题76不太一样,窗口需要重写,但同样属于不定长窗口。

/*
* 3. Longest Substring Without Repeating Characters
* 不重复的最长字串
* https://leetcode.com/problems/longest-substring-without-repeating-characters/
*
* */

/*
滑动窗口的思想下,非常简单
移动右边,直到出现重复,则开始移动左边,直到不再重复。
时间复杂度 o(n),hash加持下下,重复判断只需要O(1)
空间复杂度 o(k),k为不同字符个数,常数
Runtime: 5 ms, faster than 81.47% of Java online submissions for Longest Substring Without Repeating Characters.
        Memory Usage: 40.4 MB, less than 21.48% of Java online submissions for Longest Substring Without Repeating Characters.
*/

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int left, right, size;
        Window w = new Window();
        left = right = size = 0;
        while (right < s.length()) {
            w.add(s.charAt(right++));
            while (w.check()) {
                w.remove(s.charAt(left++));
            }
            size = Math.max(size, w.size);
        }
        return size;
    }

    class Window {

        int[] map;

        int size, idx;

        public Window() {
            map = new int[130];
        }

        public void add(char c) {
            if (++map[c] == 2) idx = c;
            ++size;
        }

        public void remove(char c) {
            --map[c];
            --size;
        }

        public boolean check() {
            if (map[idx] == 2) return true;
            return false;
        }

        public void disp() {
            for (int i = 'A'; i <= 'z'; i++) {
                System.out.print(map[i] + " ");
            }
            System.out.println();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值