前言
利用滑动窗口之前,该代码都是可以用暴力枚举进行实现的
left,right 双指针是具有单调性的 - 就是 left,right 走过的元素不会走第二遍
基本步骤
209. 长度最小的子数组
题目描述
解题思路
- 暴力枚举,枚举出所有的子数组,然后找到符合条件的最小长度子数组,时间复杂度 O(N*N)
- 滑动窗口:题目说了都是正整数,那么就是具有单调性的,如:a[0]+a[1] >= target ==> a[0]+a[1]+a[2] 一定大于target的,这时候我们就不用着急算a[2]了,我们可以先看a[1] 是否 >= target
时间复杂度:O(n)
代码实现
// 暴力
public int minSubArrayLen1(int target, int[] nums) {
int ret = Integer.MAX_VALUE;
int n = nums.length;
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += nums[j];
if(sum >= target) {
ret = Math.min(ret,j-i+1);
break;
}
}
}
return ret == Integer.MAX_VALUE ? 0 : ret;
}
// 滑动窗口:[left,right] 维护一个区间,如果该区间内的元素 >= target,那么right如果往前退的话,[left,right-1] 该区间right不管怎么动都 < target了
public int minSubArrayLen(int target, int[] nums) {
int ret = Integer.MAX_VALUE;
int n = nums.length;
int sum = 0;
for (int right = 0,left = 0; right < n; right++) {
sum += nums[right];
while (sum >= target) {
ret = Math.min(ret,right - left + 1);
sum -= nums[left++];
}
}
return ret == Integer.MAX_VALUE ? 0 : ret;
}
3. 无重复字符的最长子串
题目描述
解题思路
滑动窗口:借助一个整型数组,来标记当前遍历过的字符出现次数,如果发现当前次数 > 1,就表示区间存在重复元素了,就需要去重了【内部遍历 - 一直到当前元素次数出现 <= 1 为止】,然后就可以统计区间长度了
代码实现
public int lengthOfLongestSubstring(String ss) {
char[] s = ss.toCharArray();
int[] hash = new int[127];
int n = ss.length();
int ret = 0;
for (int right = 0,left = 0; right < n; right++) {
hash[s[right]]++;
while (hash[s[right]] > 1) {
hash[s[left++]]--;
}
ret = Math.max(ret,right-left+1);
}
return ret;
}
1004. 最大连续1的个数 III
题目描述
解题思路
滑动窗口:先用一个变量存储 0 再遍历区间出现的个数,当区间0的个数 > k 的时候,就需要去除区间第一个出现的0,然后计算区间长度了【遍历】
代码实现
public int longestOnes(int[] nums, int k) {
int n = nums.length;
int ret = 0;
for (int right = 0,left = 0, zero = 0; right < n; right++) {
if(nums[right] == 0) {
zero++;
}
while (zero > k) {
if(nums[left] == 0) {
zero--;
}
left++;
}
ret = Math.max(ret,right-left+1);
}
return ret;
}
1658. 将 x 减到 0 的最小操作数
题目描述
解题思路
滑动窗口:类似于最上面的第一题,题目最终的意思就是:x - 左边n个连续数 - 右边n个连续数 = 0;那么我们可以利用滑动窗口求出:中间未减去的数字的个数len,然后最终返回 nums.length - len
代码实现
public int minOperations(int[] nums, int x) {
int len = -1;
int n = nums.length;
int sum = 0;
for(int num : nums) sum += num;
sum -= x;
if(sum < 0) return -1;
int tmp = 0;
for (int right = 0,left = 0; right < n; right++) {
tmp += nums[right];
while (tmp > sum) {
tmp -= nums[left++];
}
if(tmp == sum) {
len = Math.max(len,right - left + 1);
}
}
return len == -1 ? -1 : n - len;
}
904. 水果成篮
题目描述
解题思路
滑动窗口:题目最终的意思(只存在两个数的最长子数组) - 借助map来进行实现
代码实现
// 存储种类,个数
HashMap<Integer,Integer> hash = new HashMap<>();
int ret = 0;
for (int right = 0, left = 0; right < fruits.length; right++) {
// 进窗口
int in = fruits[right];
hash.put(in,hash.getOrDefault(in,0) + 1);
// 判断
while (hash.size() > 2) {
// 出窗口
int out = fruits[left++];
hash.put(out,hash.get(out) - 1);
if(hash.get(out) == 0) {
hash.remove(out);
}
}
ret = Math.max(ret,right-left+1);
}
return ret;
438. 找到字符串中所有字母异位词
题目描述
解题思路
如何判断异位词:1. 两边排序,然后逐个而比较;
2. 我们直接比较字符的个数(我们用这个)
- 我们先统计 p 中每个字符的个数
- 滑动窗口:遍历 s 的区间大于 p 的时候,区间头部字符的个数就--
- 如果判断出是异位词,就存储区间开头下标
代码实现
public List<Integer> findAnagrams(String s, String p) {
List<Integer> ret = new ArrayList<>();
// 1. 先统计p的每个字符的个数
int[] hash1 = new int[26];
int m = p.length();
for (int i = 0; i < m; i++) {
char ch = p.charAt(i);
hash1[ch - 'a']++;
}
int[] hash2 = new int[26];
int n = s.length();
for (int right = 0, left = 0; right < n; right++) {
// 进窗口
char in = s.charAt(right);
hash2[in - 'a']++;
if(right - left + 1 > m) {
// 出窗口
char out = s.charAt(left);
hash2[out - 'a']--;
left++;
}
// 更新结果
if(check(hash1,hash2)) {
ret.add(left);
}
}
return ret;
}
private boolean check(int[] hash1, int[] hash2) {
for (int i = 0; i < 26; i++) {
if(hash1[i] != hash2[i]) {
return false;
}
}
return true;
}
上面的解法:我们再判断是否是字母异位词的时候,需要遍历存储个数的数组进行判断,时间复杂度比较高了,而且这里是每个字符,如果 p 是字符串数组的话,这个方法就不合适了,下面代码就是针对判断异位词的优化【引入了count遍历,只存储有效字符个数来进行判断是否是异位词】
public List<Integer> findAnagrams(String s, String p) {
List<Integer> ret = new ArrayList<>();
// 1. 先统计p的每个字符的个数
int[] hash1 = new int[26];
int m = p.length();
for (int i = 0; i < m; i++) {
char ch = p.charAt(i);
hash1[ch - 'a']++;
}
int[] hash2 = new int[26];
int count = 0; // 维护有效字符的个数
int n = s.length();
for (int right = 0, left = 0; right < n; right++) {
// 进窗口
char in = s.charAt(right);
hash2[in - 'a']++;
if(hash2[in - 'a'] <= hash1[in - 'a']) {
count++;
}
if(right - left + 1 > m) {
// 出窗口
char out = s.charAt(left);
if(hash2[out - 'a'] <= hash1[out - 'a']) {
count--;
}
hash2[out - 'a']--;
left++;
}
// 更新结果
if(count == m) {
ret.add(left);
}
}
return ret;
}
30. 串联所有单词的子串
题目描述
该题相当于上面题目的
升级版注意题目:words 中每个单词长度相同
解题思路
这是不是就是438. 找到字符串中所有字母异位词
与 438题的不同点:
代码实现
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> ret = new ArrayList<>();
// 存储 words 中字符串对应的个数
HashMap<String,Integer> hash1 = new HashMap<>();
for(String word : words) {
hash1.put(word,hash1.getOrDefault(word,0)+1);
}
int len = words[0].length();
int m = words.length; // m个单词
// 滑动窗口执行次数
for (int i = 0; i < len; i++) {
// 窗口内所有单词的频次
HashMap<String,Integer> hash2 = new HashMap<>();
// count 存储有效字符串的个数
for (int left = i, right = i, count = 0; right + len <= s.length(); right += len) {
// 进窗口 + 维护count
String in = s.substring(right,right + len);
hash2.put(in,hash2.getOrDefault(in,0)+1);
if(hash2.get(in) <= hash1.getOrDefault(in,0)) {
count++;
}
// 判断 区间字符超过words长度,就需要出窗口了
if(right - left + 1 > len * m) {
// 出窗口 + 维护count
String out = s.substring(left,left+len);
// 如果出的是有效字符串,count--
if(hash2.get(out) <= hash1.getOrDefault(out,0)) {
count--;
}
hash2.put(out,hash2.get(out)-1);
left += len;
}
// 更新结果
if(m == count) {
ret.add(left);
}
}
}
return ret;
}
76. 最小覆盖子串
题目描述
解题思路
这里还是引入count遍历,但是此时count就不是表示有效字符的个数了,而是有效字符的类别数(个数:A2,B1;A1,B2 那么count就都是3,就无法区分了,但是类别只有两类,这个就好区分)
代码实现
public String minWindow(String s, String t) { // 1. 先统计p的每个字符的个数 int[] hash1 = new int[128]; int m = t.length(); int kinds = 0; for (int i = 0; i < m; i++) { char ch = t.charAt(i); if(hash1[ch]++ == 0) kinds++; }
int[] hash2 = new int[128];
int begin = -1;
int count = 0; // 维护有效字符的个数
int n = s.length();
int minLen = Integer.MAX_VALUE;
for (int right = 0, left = 0; right < n; right++) {
// 进窗口
char in = s.charAt(right);
hash2[in]++;
if(hash2[in] == hash1[in]) {
count++;
}
while (kinds == count) {
if(right - left + 1 < minLen) {
minLen = right - left + 1;
begin = left;
}
// 出窗口
char out = s.charAt(left);
if(hash2[out] == hash1[out]) {
count--;
}
hash2[out]--;
left++;
}
}
if(begin == -1) return new String();
else return s.substring(begin, begin + minLen);
}