赞
踩
滑动窗口是一种基于双指针的一种思想,就是两个指针指向的元素之间形成一个窗口,并且左右指针方向一致的向前滑动(右端固定,左端滑动;左端固定,右端滑动)。因此,解决该问题的关键在于确定两个指针如何移动。
分类:窗口有两类,一种是固定大小类的窗口,一类是大小动态变化的窗口。
在序列中,使用双指针中的左右指针,步骤如下:
left = right = 0
,把索引区间[left, right)
称为一个窗口(注意该窗口是左开右闭
,因为初始窗口[0,0)
区间没有任何元素)。[left, right)
,直到窗口中的序列符合要求。right指针
,转而不断增加left指针
缩小窗口[left, right)
,直到窗口中的序列不再符合要求。同时,每次增加 left前,都要更新一轮结果。right指针
到达序列的尽头。注意:步骤二类似于在寻找一个
可行解
,而步骤三则是在优化可行解
,最终找到一个最优解
。
滑动窗口的求解步骤就是左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。
left,right := 0,0 // 左右指针
// 窗口右边界滑动
for right < length {
window.add(s[right++]) // 右元素进窗,且右指针加1
// 窗口满足条件,寻找最优解(右指针不动,左指针开始移动)
for valid(window) && left<right {
... // 满足条件后的操作,可以记录或者更新全局数据
window.remove(arr[left++]) // 左元素出窗,且左指针移动,直到窗口不满足条件
}
}
left,right := 0,0 // 左右指针
// 窗口右边界滑动
for right < length {
// 不满足可行解条件,则扩大窗口
if !valid(window){
window.add(s[right++]) // 右元素进窗,且右指针加1
}else{
... // 满足条件后的操作,可以记录或者更新全局数据
window.remove(arr[left++]) // 左元素出窗,且左指针移动,直到窗口不满足条件
}
}
给定两个字符串 s 和 p,找到 s 中所有 p 的异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词指由相同字母重排列形成的字符串(包括相同的字符串)。输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。示例 2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词
分析:该问题为固定滑动窗口问题,对于p的异位词,其长度一定与字符串p的长度相等,因此,可以在字符串s中维护一个与字符串p长度相等且固定长度的滑动窗口。当窗口中每种字符的数量与字符串p中每种字符的数量相同时,则该窗口内的字符串与字符串p为异位词。
对于滑动窗口中每种字符的数量统计与字符串p中每种字符的数量统计可以利用字符的ASCII码来映射到数组中进行统计。如,对于字符b,可以通过 ′ b ′ − ′ a ′ = 1 'b'-'a'=1 ′b′−′a′=1 来映射到数组中。
那么在窗口滑动过程中,固定其窗口长度,并且分别统计窗口与字符串p中字符的数量。当两个数组中每个分量的数据相同时,则表示其为一个异位词。因此创建两个数组,window_count
以及p_count
用于统计。
public List<Integer> findAnagrams(String s, String p) { int s_len = s.length(), p_len = p.length(); List<Integer> ans = new ArrayList<Integer>();//用于记录窗口滑动过程中的可行解 //字符s的长度一定要大于字符p的长度,否则不存在异位词 if (s_len < p_len) { return ans; } int[] window_count = new int[26]; //用于维护窗口滑动过程中每个字符的数量 int[] p_count = new int[26]; //用于统计字符p的每个字符数量 //初始统计 for (int i = 0; i < p_len; i++) { window_count[s.charAt(i) - 'a']++; p_count[p.charAt(i) - 'a']++; } //如果窗口最初始的时候满足异位词,则将0加入到ans数组中 if (Arrays.equals(window_count, p_count)) ans.add(0); //窗口开始滑动,左右都按照同频率滑动 for (int i = 0; i < s_len - p_len; i++) { window_count[s.charAt(i) - 'a']--; // 左指针移动 window_count[s.charAt(i + p_len) - 'a']++; // 右指针移动 //判断是否满足异位词的条件,满足加入到ans中 if (Arrays.equals(window_count, p_count)) ans.add(i + 1); } return ans; }
对于以上的代码中,发现对于统计字符串p的字符数量数组p_count
只统计了一次之后就没有变动。因此可以考虑只统计滑动窗口中字符和字符串p中每种字母数量的差,存于数组count
中,当count
数组中所有的差都为0的时候,表示滑动窗口中的字符与字符串p构成异位词。该方案减少了对字符串p的统计内存消耗,在空间上进行了优化。
当然,在考虑统计滑动窗口和字符串p中每种字母数量的差,存于数组count
中后, 是否可以通过一个变量diff用于记录滑动窗口中的字符与字符串p中数量不同的字母的个数,也就是count
数组中不为0的个数。当diff值等于0之后,表示滑动窗口中的字符和字符串p中每种字母数量的差都为0,即构成异位词。对于该方案,解决的关键在于如何在窗口滑动过程维护diff变量。
比如说,字符
abbbb
与aaaaa
的count
数组为 [ − 4 , 4 , 0 , . . . ] [-4,4,0,...] [−4,4,0,...] ,该diff值为2。字符abbbb
与字符abccc
的count
数组为 [ 0 , 3 , − 3 , 0 , . . . ] [0,3,-3,0,...] [0,3,−3,0,...] ,该diff值也为2。
diff变量的变化主要在于窗口滑动过程中。而字符串p不发生改变,那么对于窗口滑动过程中,主要根据窗口中左右指针所指向的字母的变化从而改动count
数组,进而改变diff的值:
s[left]
导致diff变化情况有:如果在数组count
中,字符s[left]
所对应的值为1或者0时,会导致窗口滑动一个单位长度,会使得count
数组中0的个数会变化,进而导致diff值发生改变。s[right]
导致diff变化情况有:如果在数组count
中,字符s[right]
所对应的值为-1或者0时,也会导致窗口滑动一个单位长度,会导致count
数组中0的个数发生变化,进而导致diff值发生改变。public List<Integer> findAnagrams(String s, String p) { int s_len = s.length(), p_len = p.length(); List<Integer> ans = new ArrayList<Integer>();//用于记录窗口滑动过程中的可行解 //字符s的长度一定要大于字符p的长度,否则不存在异位词 if (s_len < p_len) { return ans; } int[] count = new int[26]; //用于维护窗口滑动过程中窗口中的字符与字符p中每个字符的差值 int diff = 0; // 用于维护count数组中不为0的个数 //初始统计 for (int i = 0; i < p_len; i++) { count[s.charAt(i) - 'a']++; count[p.charAt(i) - 'a']--; } // 统计count数组中不为0的个数,并赋值给diff for (int i = 0; i < count.length; i++) { if (count[i] != 0) diff++; } //如果窗口最初始的时候满足异位词,则将0加入到ans数组中 if (diff == 0) ans.add(0); //窗口开始滑动,左右都按照同频率滑动 for (int i = 0; i < s_len - p_len; i++) { //左指针移动导致diff变化的情况 if (count[s.charAt(i) - 'a'] == 1) { diff--; } else if (count[s.charAt(i) - 'a'] == 0) { diff++; } count[s.charAt(i) - 'a']--; // 左指针移动 //右指针移动导致diff变化的情况 if (count[s.charAt(i + p_len) - 'a'] == -1) { diff--; } else if (count[s.charAt(i + p_len) - 'a'] == 0) { diff++; } count[s.charAt(i + p_len) - 'a']++; // 右指针移动 //判断是否满足异位词的条件,满足加入到ans中 if (diff == 0) ans.add(i + 1); } return ans; }
java细节优化,在java版本19.0.2中,对于charAt()
的实现中有对数组下标进行越界判断:
public static <X extends RuntimeException>
int checkIndex(int index, int length,
BiFunction<String, List<Number>, X> oobef) {
if (index < 0 || index >= length)
throw outOfBoundsCheckIndex(oobef, index, length);
return index;
}
如果要考虑这部分的性能优化,在java中对于字符串的遍历,考虑将字符串通过方法
toCharArray()
将字符串转化为字符数组。
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。示例 2:
输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
public String minWindow(String s, String t) { // 定义一个长度为128的int数组,用于记录每个字符出现的次数 //每个数组的下标代表着该字符的ASCII数值 int[] chars = new int[128]; // 定义一个长度为128的boolean数组,用于标记每个字符是否在t字符串中出现 boolean[] flag = new boolean[128]; // 统计字符t中的字符情况 for (int i = 0; i < t.length(); i++) { // 标记t字符串中出现的字符 flag[t.charAt(i)] = true; // 统计t字符串中每个字符出现的次数 ++chars[t.charAt(i)]; } // 定义变量cnt、l、min_l、min_size,分别表示当前滑动窗口中包含t字符串的字符个数、窗口左端点、最小子串的左端点和长度。 int cnt = 0, l = 0, min_l = 0, min_size = s.length() + 1; // 滑动窗口 for (int r = 0; r < s.length(); ++r) { // 如果当前字符在t字符串中出现 if (flag[s.charAt(r)]) { if (--chars[s.charAt(r)] >= 0) { ++cnt; } // 若目前滑动窗口已包含T中全部字符, // 则尝试将l右移, 在不影响结果的情况下获得最短子字符串 while (cnt == t.length()) { // 如果当前滑动窗口已经包含t字符串中的所有字符 // 判断当前子串的长度是否小于之前求得的最小子串长度 if (r - l + 1 < min_size) { min_l = l; // 更新最小子串的左端点 min_size = r - l + 1; // 更新最小子串的长度 } // 将窗口左端点l右移,缩小窗口大小,如果l对应的字符在t字符串中出现,并且其在chars数组中对应的值加1后大于0,则表示当前窗口中已经不包含该字符的所有出现次数 if (flag[s.charAt(l)] && ++chars[s.charAt(l)] > 0) { --cnt; } ++l; // 将窗口左端点l右移 } } } // 返回最小子串,如果不存在,则返回空字符串。 return min_size > s.length() ? "" : s.substring(min_l, min_l + min_size); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。