算法:【滑动窗口】
滑动窗口的思想:
用 i,j 表示滑动窗口的左边界和右边界,通过改变i,j来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度j-i+1,这些长度中的最小值就是要求的结果。
1. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解题思路:
在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 rr 指针,和一个用于「收缩」窗口的 ll 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 ss 上滑动窗口,通过移动 rr 指针不断扩张窗口。当窗口包含 tt 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
class Solution { public String minWindow(String s, String t) { if (s == null || s.length() == 0 || t == null || t.length() == 0){ return ""; } int[] need = new int[128]; //记录需要的字符的个数 for (int i = 0; i < t.length(); i++) { need[t.charAt(i)]++; } //l是当前左边界,r是当前右边界,size记录窗口大小,count是需求的字符个数,start是最小覆盖串开始的index int l = 0, r = 0, size = Integer.MAX_VALUE, count = t.length(), start = 0; //遍历所有字符 while (r < s.length()) { char c = s.charAt(r); if (need[c] > 0) {//需要字符c count--; } need[c]--;//把右边的字符加入窗口 if (count == 0) {//窗口中已经包含所有字符 while (l < r && need[s.charAt(l)] < 0) { need[s.charAt(l)]++;//释放右边移动出窗口的字符 l++;//指针右移 } if (r - l + 1 < size) {//不能右移时候挑战最小窗口大小,更新最小窗口开始的start size = r - l + 1; start = l;//记录下最小值时候的开始位置,最后返回覆盖串时候会用到 } //l向右移动后窗口肯定不能满足了 重新开始循环 need[s.charAt(l)]++; l++; count++; } r++; } return size == Integer.MAX_VALUE ? "" : s.substring(start, start + size); } }
2. 找到字符串中所有字母异位词
class Solution { public List<Integer> findAnagrams(String s, String p) { //记录p的所有字母及其个数 char[] need = new char[26]; for (int i = 0; i < p.length(); i++) { need[p.charAt(i) - 'a']++; } //start和end分别控制窗口的前端和后端 int start = 0, end = 0; char[] window = new char[26]; List<Integer> ans = new ArrayList<Integer>(); while (end < s.length()) { window[s.charAt(end) - 'a']++; //加入窗口数据 while (end - start + 1 == p.length()) { //维护一个长度为p.length()的窗口,并更新答案 if (Arrays.equals(window, need)) ans.add(start); window[s.charAt(start) - 'a']--; start++; } end++; } return ans; } }