十六 滑动窗口专题

滑动窗口首先按照类型分,可以分为定长窗口和变长窗口。
定长窗口也就是说题目给出来的时候,你知道这个窗口的长度是固定的,一般会要求你返回每个这样的窗口。我们只需要处理I,J 2根指针同时向后移一位。然后每次的位移过程里做的事是需要技巧的。

定长窗口典型题目有

Given an integer array A and a sliding window of size K, find the maximum value of each window as it slides from left to right.

Assumptions

The given array is not null and is not empty

K >= 1, K <= A.length

Examples

A = {1, 2, 3, 2, 4, 2, 1}, K = 3, the windows are {{1,2,3}, {2,3,2}, {3,2,4}, {2,4,2}, {4,2,1}},

and the maximum values of each K-sized sliding window are [3, 3, 4, 4, 4]

或者
Find all occurrence of anagrams of a given string s in a given string l. Return the list of starting indices.

Assumptions

s is not null or empty.
l is not null.
Examples

l = "abcbac", s = "ab", return [0, 3] since the substring with length 2 starting from index 0/3 are all anagrams of "ab" ("ab", "ba").

上面第一个题目的技巧是维护一个单调DEQUE,我们需要思考的是一个窗口里的最大值取决于什么,有哪些元素对最大值是完全没有贡献的。我们发现,一个位置靠前并且比这个窗口里后面的值还小的那个数 是不可能对之后窗口的最大值产生价值。所以我们可以之前把它丢掉。根据这个原则。我们DEQUE的最左端是该窗口的最大值。同时DEQUE维持了单调递减的特性。后面继续保持这个特性即可。

public List<Integer> maxWindows(int[] array, int k) {
    // Write your solution here
    Deque<Integer> dq = new ArrayDeque<>();
    if (k > array.length) k = array.length;
    for (int i = 0; i < k; i++) {
      while (!dq.isEmpty() && array[i] > dq.peekLast()) dq.pollLast();
      dq.offerLast(array[i]);
    }
    List<Integer> res = new ArrayList<>();
    res.add(dq.peekFirst());
    for (int i = k; i < array.length; i++) {
      if (array[i - k] == dq.peekFirst()) dq.pollFirst();
      while (!dq.isEmpty() && array[i] > dq.peekLast()) dq.pollLast();
      dq.offerLast(array[i]);
      res.add(dq.peekFirst());
    }
    return res;
  }

第二道题,就是要看什么时候需要去更新CNT变量,只有当S里的字符的计数是正的时候,删除的时候需要CNT--。是》=0的时候,增加的时候需要CNT++。 因为无用的字符,前面没有初始化。所以增加的时候那时的值必然为负数。

public List<Integer> allAnagrams(String sh, String lo) {
    int i = 0;
    int[] map = new int[256];
    for (char c : sh.toCharArray()) map[c]++;
    int cnt = sh.length();
    List<Integer> res = new ArrayList<>();
    if (lo.length() < sh.length()) return res;
    int j = 0;
    while (j < sh.length()) {
     if (map[lo.charAt(j++)]-- > 0){
       cnt--;
     }
    }
    if (cnt == 0) res.add(i);
    while (j < lo.length()) {
      if (map[lo.charAt(i++)]++ >= 0){
       cnt++;
      }
      if (map[lo.charAt(j++)]-- > 0) {
        cnt--;
      }
      if (cnt == 0) res.add(i);
    }
    
    return res;
  }

其他定长窗口
Given a string containing only 'A', 'B', 'C', 'D', return the number of occurrences of substring which has length 4 and includes all of the characters from 'A', 'B', 'C', 'D' with in descending sorted order.

Assumptions:

The given string is not null and has length of >= 4.
In the output, if two substrings have the same frequency, then they should be sorted by the their natural order.
Examples:

“ABCDABCDD”, --> {"ABCD" : 2, "BCDA" : 1, "CDAB" : 1, "DABC" : 1}

/**
 * public class Frequency {
 *   public String str;
 *   public int freq;
 *   public Frequency(String str, int freq) {
 *     this.str = str;
 *     this.freq = freq;
 *   }
 * }
 */
public class Solution {
  public List<Frequency> frequency(String input) {
    Map<String,Integer> map = new HashMap<>();
    int[] m = new int[4];
    int i = 0;
    int cnt = 0;
    char[] cs = input.toCharArray();
    for (; i < 4; i++) {
      if(m[cs[i] - 'A']++ == 0) cnt++;
    }
    for (; i <= cs.length; i++) {
      if (cnt == 4) {
        String key = input.substring(i - 4, i);
        map.putIfAbsent(key,0);
        map.put(key, map.get(key) + 1);
      }
      if (i == cs.length) break;
      if(--m[cs[i - 4] - 'A'] == 0) cnt--;
      if(m[cs[i] - 'A']++ == 0) cnt++;
    }
    List<Frequency> res = new ArrayList<Frequency>();
    for (String key : map.keySet()) {
      res.add(new Frequency(key, map.get(key)));
    }
    Collections.sort(res, new Comparator<Frequency> () {
      public int compare(Frequency a, Frequency b) {
        if (a.freq == b.freq) return a.str.compareTo(b.str);
        return b.freq - a.freq;
      }
    });
              
    return res;
  }
}

下面我们来看变长的窗口。

变长窗口最让人头疼的问题是很容易,移着移着指针就混乱了,一跑代码不是有错误,就是INDEX越界。所以基于这个问题,我设计了一套针对滑动窗口的不变量来避免写代码的时候发生想不清楚而引入BUG或者死循环指针越界的问题。

变长窗口会有2类 一般会让你找最长的窗口,或者最短的窗口。所以道题滑动变长窗口题,可以仅仅改一个单词,就让题目变化。
比如:
Given a string, return the longest contiguous substring that contains exactly k type of characters.

Return null if there does not exist such substring.

Assumptions:

The given string is not null.
k >= 0.
Examples:

input = "aabcc", k = 3, output = "aabcc".
input = "aabcccc", k = 2, output = "bcccc".

变化:
Given a string, return the shortest contiguous substring that contains exactly k type of characters.

Return an empty string if there does not exist such substring.

Assumptions:

The given string is not null.
k >= 0.
Examples:

input = "aabcc", k = 3, output = "abc".
input = "aabbbcccc", k = 3, output = "abbbc".
input = "aabcc", k = 4, output = "".

如果求最长,也就是在窗口满足条件的时候,移动后面一个(J)指针。
如果求最短,则相反,当窗口满足条件时,移动前一个(I)指针。

根据上述思想,我们要思考I,J的物理意义
我个人一般喜欢定义窗口区间为前闭,后开
这样给I,J 赋初始值的时候可以为0,0

那么循环退出一般是J 》 ARRAY.LENGTH的时候
所以我一般会用如下框架解决滑动窗口。
先看找最小窗口

        int min = Integer.MAX_VALUE;
        int i = 0, j = 0;
        while (j <= array.length) {
            if (满足条件) {
                min = Math.min(min, j - i);
                updateState(i++)
            } else {
                if (j == array.length) break;
                updateState(j++)
            }
        }
        return min;

最长窗口

        int max= 0;
        int i = 0, j = 0;
        while (j <= array.length) {
            if (窗口需要增大) {
                if (满足条件)
                    max= Math.max(max, j - i);
                if (j == array.length) break;
                updateState(j++)
            } else {
                updateState(i++)
            }
        }
        return max;

根据这2个设计思路,我们可以很轻松的解决上面2个问题。

public String shortest(String input, int k) {
    char[] cs = input.toCharArray();
    int min = cs.length;
    int s = 0, e = 0;
    int i = 0, j = 0;
    int[] map = new int[256];
    if (k == 0) return "";
    while (j <= cs.length) {
      if (k == 0) {
        if (j - i < min) {
          min = j - i;
          s = i;
          e = j;
        }
        if (++map[cs[i++]] == 0) k++;
      } else {
        if (j == cs.length) break;
        if (map[cs[j++]]-- == 0) k--;
      }
    }
    return input.substring(s,e);
  }

最长

public String longest(String input, int k) {
    char[] cs = input.toCharArray();
    int max = 0;
    int s = 0, e = 0;
    int i = 0, j = 0;
    int[] map = new int[256];
    if (k == 0) return "";
    while (j <= cs.length) {
      if (k >= 0) {
        if (k == 0 && j - i > max) {
          max = j - i;
          s = i;
          e = j;
        }
        if (j == cs.length) break;
        
        if (map[cs[j++]]-- == 0) k--;
        
      } else {
        if (++map[cs[i++]] == 0) k++;
      }
    }
    return s == e ? null : input.substring(s,e);
  }

我们再来看几道别的题,是不是都差不多了。

Minimum Window Substring

Given a string S and a string T, find the minimum window in S which will contain all the characters in T

Input: S = “ADOBECODEBANC”

      T = “ABC”

Output: “BANC”

public String minWindow(String source, String target) {
    int[] map = new int[256];
    for (char c : target.toCharArray()) {
      map[c]++;
    }
    int i = 0, j = 0;
    char[] cs = source.toCharArray();
    int cnt = target.length();
    if (cnt == 0) return target;
    int min = source.length();
    int s = 0, e = 0;
    while (j <= cs.length) {
      if (cnt == 0) {
        if (j - i < min) {
          min = j - i;
          s = i;
          e = j;
        }
        if (map[cs[i++]]++ == 0) cnt++;
      } else {
        if (j == cs.length) break;
        if (map[cs[j++]]-- > 0) cnt--;
      }
    }
    return source.substring(s,e);
  }

Smallest Substring Containing All Characters Of Another String

Given two strings s1 and s2, find the shortest substring in s1 containing all characters in s2.

If there does not exist such substring in s1, return an empty string.

Assumptions:

s1 and s2 are not null or empty.
Examples:

s1= “The given test strings”

s2: “itsst”

Output string: “st stri”

public String smallest(String s1, String s2) {
    int[] map = new int[256];
    for (char c : s2.toCharArray()) map[c]++;
    int cnt = s2.length();
    int i = 0, j = 0;
    char[] cs = s1.toCharArray();
    int min = Integer.MAX_VALUE, s = 0, e = 0;
    while (j <= cs.length) {
      if (cnt == 0) {
        if (j - i < min) {
          min = j - i;
          s = i;
          e = j;
        }
        if (++map[cs[i++]] > 0) cnt++;
      } else{
        if (j == cs.length) break;
        if (map[cs[j++]]-- > 0) cnt--;
      }
    }
    return s1.substring(s,e);
  }

Minimum Size Subarray Sum

Given an array of n positive integers and a positive integer s, find the minimal length of a contiguous subarray of which the sum ≥ s. If there isn't one, return 0 instead.

For example, given the array [2,3,1,2,4,3] and s = 7,
the subarray [4,3] has the minimal length under the problem constraint.

public int minSubArrayLen(int num, int[] nums) {
    int i = 0, j = 0, min = Integer.MAX_VALUE;
    if (num <= 0) return 0;
    while (j <= nums.length) {
      if (num <= 0) {
        min = Math.min(min, j - i);
        num += nums[i++];
      } else {
        if (j == nums.length) break;
        num -= nums[j++];
      }
    }
    return min == Integer.MAX_VALUE ? 0 : min;
  }

最长的变长窗口

Longest Substring with At Most Two Distinct Characters

Given a string, find the the longest substring T that contains at most 2 distinct characters.

For example, Given s = “eceba”, T is "ece"

public int lengthOfLongestSubstringTwoDistinct(String input) {
        char[] cs = input.toCharArray();
        int i = 0, j = 0;
        int k = 2;
        int[] map = new int[256];
        int max = 0;
        while (j <= cs.length) {
            if (k >= 0) {
              max = Math.max(max, j - i);
              if (j == cs.length) break;
              if (map[cs[j++]]++ == 0) k--;
            } else {
              if (--map[cs[i++]] == 0) k++;
            } 
        }
        return max;
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,325评论 0 10
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,793评论 0 38
  • pyspark.sql模块 模块上下文 Spark SQL和DataFrames的重要类: pyspark.sql...
    mpro阅读 9,451评论 0 13
  • 保持乐观的心态吧!这句话不仅仅是写给这篇文章的读者,更重要的是想要写给我自己! 能够长久的保持乐观实在是一件很厉害...
    栉风沐雨1阅读 607评论 0 1
  • 感觉中间有点空,所以又加了两片叶子。 今天临摹了一副葡萄风信子。
    晨月悦阅读 131评论 0 0