滑动窗口
刷了点题之后发现,滑动窗口的题,基本都是找的是一个区间(或者是这个区间的大小长度),从左到右的遍历数组(也可以是别的数据结构)时,这个区间(也就是滑动窗口)会有所改变。
还有一点就是这个区间时连在一起的,所以类似于 子串 之类的问题,大概率就是用滑动窗口
通用框架
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
public String slidingWindow(String s, String t) {
for (int i = 0; i < t.length(); i ++) {
char c = t.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = -1;
while (right < s.length()) {
right += 1;
// 自定义
// ...
while (每次都检测窗口左边界是否可以往右移动) {
// 自定义
// ...
left += 1;
}
}
}
}
和为s的连续正数序列
// https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/
class Solution {
public int[][] findContinuousSequence(int target) {
int left = 1, right = 1;
int sum = 1;
List<int[]> res = new ArrayList<>();
while (right < target / 2 + 1 && left <= target / 2 + 1) {
right += 1;
sum += right;
while (sum >= target) {
if (sum == target) {
int[] ans = new int[right - left + 1];
for (int i = left; i <= right; i ++)
ans[i - left] = i;
res.add(ans);
}
sum -= left;
left += 1;
}
}
return res.toArray(new int[0][]);
}
}
最小覆盖子串
// https://leetcode-cn.com/problems/minimum-window-substring/
// 定义最后答案的左边界、右边界以及最短长度。每次都在右边添加一个元素,并同时检测可不可以将滑动窗口左边界往右移动,同时更新滑动窗口的状态
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
int ansL = -1;
int ansR = -1;
int len = Integer.MAX_VALUE;
public String minWindow(String s, String t) {
for (int i = 0; i < t.length(); i ++) {
char c = t.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = -1;
while (right < s.length() - 1 && left < s.length()) {
right += 1;
if (right < s.length() && need.containsKey(s.charAt(right))) {
window.put(s.charAt(right), window.getOrDefault(s.charAt(right), 0) + 1);
}
while (check() && left <= right) {
if (right - left + 1 < len) {
ansL = left;
ansR = right;
len = right - left + 1;
}
if (need.containsKey(s.charAt(left))) {
window.put(s.charAt(left), window.getOrDefault(s.charAt(left), 0) - 1);
}
left += 1;
}
}
return ansR == -1 ? "" : s.substring(ansL, ansR + 1);
}
boolean check() {
for (Map.Entry e : need.entrySet()) {
int val = (int) e.getValue();
if (window.getOrDefault(e.getKey(), 0) < val) {
return false;
}
}
return true;
}
}
// https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-by-chuancey-hkwu/
// 这个是 labuladong 的解法,不需要每次判断是否收缩时都检查一遍map,但是不知道为什么leetcode过不了
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
int ansL = -1;
int ansR = -1;
int len = Integer.MAX_VALUE;
public String minWindow(String s, String t) {
for (int i = 0; i < t.length(); i ++) {
char c = t.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = 0;
int vaild = 0;
while (right < s.length()) {
char tmpChar1 = s.charAt(right);
right += 1;
if (need.containsKey(tmpChar1)) {
window.put(tmpChar1, window.getOrDefault(tmpChar1, 0) + 1);
if (need.get(tmpChar1) == window.get(tmpChar1)) {
vaild += 1;
}
}
while (vaild == need.size()) {
if (right - left < len) {
ansL = left;
ansR = right;
len = right - left;
}
char tmpChar2 = s.charAt(left);
left += 1;
if (need.containsKey(tmpChar2)) {
if (need.get(tmpChar2) == window.get(tmpChar2)) {
vaild -= 1;
}
window.put(tmpChar2, window.get(tmpChar2) - 1);
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring(ansL, ansR);
}
}
字符串的排列
// https://leetcode-cn.com/problems/permutation-in-string/
// 改自上题的第一解
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
public boolean checkInclusion(String s1, String s2) {
for (int i = 0; i < s1.length(); i ++) {
char c = s1.charAt(i);
need.put(c, need.getOrDefault(c, 0) + 1);
}
int left = 0;
int right = -1;
while (right < s2.length() - 1 && left < s2.length()) {
right += 1;
if (right < s2.length() && need.containsKey(s2.charAt(right))) {
window.put(s2.charAt(right), window.getOrDefault(s2.charAt(right), 0) + 1);
}
// 在是否收缩的情况判断
while (right - left + 1 >= s1.length()) {
// 先判断总体长度是否符合,在判断字符是否都对的上
if (check()) {
return true;
}
if (need.containsKey(s2.charAt(left))) {
window.put(s2.charAt(left), window.getOrDefault(s2.charAt(left), 0) - 1);
}
left += 1;
}
}
return false;
}
boolean check() {
for (Map.Entry e : need.entrySet()) {
int val = (int) e.getValue();
if (window.getOrDefault(e.getKey(), 0) != val) {
return false;
}
}
return true;
}
}
// 跟上题一样,C 改成 Java,就跑不通了,以后研究
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
public boolean checkInclusion(String s1, String s2) {
for (int i = 0; i < s1.length(); i ++) {
need.put(s1.charAt(i), need.getOrDefault(s1.charAt(i), 0) + 1);
}
int left = 0;
int right = 0;
int vaild = 0;
while (right < s2.length()) {
char tmpChar1 = s2.charAt(right);
right += 1;
if (need.containsKey(tmpChar1)) {
window.put(tmpChar1, window.getOrDefault(tmpChar1, 0) + 1);
if (need.get(tmpChar1) == window.get(tmpChar1)) {
vaild += 1;
}
}
while (right - left >= s1.length()) {
if (vaild == need.size()) {
return true;
}
char tmpChar2 = s2.charAt(left);
left += 1;
if (need.containsKey(tmpChar2)) {
if (need.get(tmpChar2) == window.get(tmpChar2)) {
vaild -= 1;
}
window.put(tmpChar2, window.get(tmpChar2) - 1);
}
}
}
return false;
}
}
找到字符串中所有字母异位词
// https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/
// 基本上跟上题一样,多个记录 left 的步骤
class Solution {
Map<Character, Integer> need = new HashMap<>();
Map<Character, Integer> window = new HashMap<>();
public List<Integer> findAnagrams(String s, String p) {
for (int i = 0; i < p.length(); i ++) {
need.put(p.charAt(i), need.getOrDefault(p.charAt(i), 0) + 1);
}
List<Integer> lis = new ArrayList<>();
int left = 0;
int right = -1;
while (right < s.length() - 1 && left < s.length()) {
right += 1;
if (right < s.length() && need.containsKey(s.charAt(right))) {
window.put(s.charAt(right), window.getOrDefault(s.charAt(right), 0) + 1);
}
while (right - left + 1 >= p.length()) {
if (check()) {
lis.add(left);
}
if (need.containsKey(s.charAt(left))) {
window.put(s.charAt(left), window.get(s.charAt(left)) - 1);
}
left += 1;
}
}
return lis;
}
boolean check() {
for (Map.Entry e : need.entrySet()) {
int val = (int) e.getValue();
if (window.getOrDefault(e.getKey(), 0) != val) {
return false;
}
}
return true;
}
}
无重复字符的最长子串
// https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
class Solution {
Map<Character, Integer> window = new HashMap<>();
int len = 0;
public int lengthOfLongestSubstring(String s) {
int left = 0;
int right = -1;
while (left < s.length() && right < s.length() - 1) {
right += 1;
window.put(s.charAt(right), window.getOrDefault(s.charAt(right), 0) + 1);
// check是用来判断是否需要收缩
while (checkIn()) {
window.put(s.charAt(left), window.get(s.charAt(left)) - 1);
left += 1;
}
// 更新边界
if (right - left + 1 > len) {
len = right - left + 1;
}
}
return len;
}
boolean checkIn() {
for (Map.Entry e : window.entrySet()) {
int val = (int) e.getValue();
if (val > 1) {
return true;
}
}
return false;
}
}
// 简便版本,
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int i = -1;
int res = 0;
for (int j = 0; j < s.length() ; j ++) {
if (map.containsKey(s.charAt(j))) {
i = Math.max(i,map.get(s.charAt(j)));
}
map.put(s.charAt(j), j);
res = Math.max(res, j - i);
}
return res;
}
}
滑动窗口的扩展
去除重复字母
// https://leetcode-cn.com/problems/remove-duplicate-letters/
// 解题思路:首先用 count 记录字符串每个字符的个数和,再用 StringBuilder 作为滑动窗口载体,并用 vis 记录在窗口中的是否出现对应字符,每次 StringBuilder 加入一个字符 ch 之前,首先判断窗口是否右该字符,没有则加入,然后循环判断 StringBuilder 尾部的字符是否满足:1. 大于 ch;2. 字符串后面是否还有该尾部字符,如果满足这两条条件,则删除该尾部字符,反复上述操作直到获得结果
class Solution {
public String removeDuplicateLetters(String s) {
boolean[] vis = new boolean[26];
int[] count = new int[26];
for (int i = 0; i < s.length(); i ++) {
count[s.charAt(i) - 'a'] += 1;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i ++) {
char ch = s.charAt(i);
if (!vis[ch - 'a']) {
while (sb.length() > 0 && sb.charAt(sb.length() - 1) > ch) {
if (count[sb.charAt(sb.length() - 1) - 'a'] > 0) {
vis[sb.charAt(sb.length() - 1) - 'a'] = false;
sb.deleteCharAt(sb.length() - 1);
}
// 注意这个 else,表示若尾部元素在字符串后面没有了,则需要保存这个元素及其前面的元素,由于前面的元素肯定有比尾部元素小,为了保证最小的字符序,那么前面的就不可以删除了,所有才有了此处的 b
else {
break;
}
}
vis[ch - 'a'] = true;
sb.append(ch);
}
count[ch - 'a'] -= 1;
}
return sb.toString();
}
}
替换后的最长重复字符
// https://leetcode-cn.com/problems/longest-repeating-character-replacement/
class Solution {
public int characterReplacement(String s, int k) {
int left = 0, right = -1;
int len = s.length();
int[] records = new int[26];
int max = 0;
while (right < len - 1 && left < len) {
right += 1;
records[s.charAt(right) - 'A'] += 1;
max = Math.max(max, records[s.charAt(right) - 'A']);
// 注意这里用的是 if,用 if 虽然最后的区间里面的数不满足条件,但是这个区间大小是最大的区间大小,这里的逻辑是不让区间变小
if (right - left + 1 - max > k) {
records[s.charAt(left) - 'A'] -= 1;
left += 1;
}
}
return right - left + 1;
}
}
// 正常的版本,每次都收缩到满足条件
class Solution {
public int characterReplacement(String s, int k) {
int left = 0, right = -1;
int len = s.length();
int[] records = new int[26];
int max = 0;
int res = 0;
while (right < len - 1 && left < len) {
right += 1;
records[s.charAt(right) - 'A'] += 1;
max = Math.max(max, records[s.charAt(right) - 'A']);
while (right - left + 1 - max > k) {
records[s.charAt(left) - 'A'] -= 1;
left += 1;
}
if (right - left + 1 > res) res = right - left + 1;
}
return res;
}
}
最大连续1的个数 III
// https://leetcode-cn.com/problems/max-consecutive-ones-iii/
class Solution {
public int longestOnes(int[] nums, int k) {
int left = 0;
int right = -1;
int len = nums.length;
int record = 0;
while (right < len - 1 && left < len) {
right += 1;
if (nums[right] == 1) record += 1;
// 用的是 if
if (right - left + 1 - record > k) {
if (nums[left] == 1) record -= 1;
left += 1;
}
}
return right - left + 1;
}
}