文章目录
- 滑动窗口
- 长度最小的子数组
- 无重复字符的最长子串
- 最大连续1个个数 III
- 将x减到0的最小操作数
- 水果成篮
- 找到字符串中所有字母异位词
- 串联所有单词的子串
- 最小覆盖子串
- 总结
滑动窗口
长度最小的子数组
做这道题时,脑子里大概有个印象,知道要用滑动窗口,但是对于滑动窗口为什么就对,不会落情况吗?对于这一点不是很清楚.
emmm虽然独自做出来了,但是还是在怀疑滑动窗口的正确性.
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int sum = 0;
int min = Integer.MAX_VALUE;
for(int right = 0; right < nums.length;right++) {
sum += nums[right];
if(sum >= target) {
while(sum >= target) {
sum -= nums[left++];
}
int tmp = right - left + 2;
if(min > tmp) {
min = tmp;
}
}
}
return min==Integer.MAX_VALUE?0:min;
}
}
看完题解后,明白了.
看题解后的代码:
- 使用了Math.min(),加快了做题速度,在比赛中可以省下时间.
- 其他的都差不多~
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int sum = 0;
int min = Integer.MAX_VALUE;
for(int right = 0; right < nums.length;right++) {
sum += nums[right];
while(sum >= target) {
min = Math.min(min,right-left+1);
sum -= nums[left++];
}
}
return min==Integer.MAX_VALUE?0:min;
}
}
无重复字符的最长子串
自己使用滑动窗口+哈希表做出来了.
最开始想只用滑动窗口解决,结果想不出来.然后就想到了hash.
刚开始写时没写对,后来画了两次图,根据图,然后就写出来了~
看题解前:
class Solution {
public int lengthOfLongestSubstring(String s) {
int max = 0;
int n = s.length();
char[] ss = s.toCharArray();
boolean[] hash = new boolean[1080];
int left = 0,right = 0;
while (right < n) {
if(!hash[ss[right]]) {
hash[ss[right++]] = true;
max = Math.max(max,right-left);
}else {
hash[ss[left++]] = false;
}
}
return max;
}
}
看题解后:
- 我用的boolean,他用的int.都差不多吧.
- 还有就是我的hash数组给大了.
class Solution {
public int lengthOfLongestSubstring(String s) {
int max = 0;
int n = s.length();
char[] ss = s.toCharArray();
int[] hash = new int[128];
int left = 0,right = 0;
while (right < n) {
hash[ss[right]]++;
while(hash[ss[right]] > 1) {
hash[ss[left++]]--;
}
max = Math.max(max,right-left+1);
right++;
}
return max;
}
}
最大连续1个个数 III
自己写的代码,能过,但是看起来不美观,效率也不够高.
class Solution {
public int longestOnes(int[] nums, int k) {
int left = 0, right = 0;
int n = nums.length;
int max = 0;
while (right < n) {
if (nums[right] == 1) {
max = Math.max(max, right - left + 1);
right++;
}else{
if (k > 0) {
max = Math.max(max, right - left + 1);
right++;
k--;
} else {
while(nums[left]==1) {
left++;
}
k++;
left++;
}
}
}
return max;
}
}
看题解:
- emm,只能说没有对比,就没有伤害.人家写的代码又少又快~
- 题解使用了for循环,这样可以少写几个if,比如说当nums[right]==1时,我们可以忽略,自动让right++就行;
- 与我写的不同的是,他的k可以小于0,而我的k不能.
class Solution {
public int longestOnes(int[] nums, int k) {
int max = 0;
for(int right = 0,left = 0;right < nums.length;right++) {
if(nums[right] == 0) k--;
while(k < 0)
if(nums[left++] == 0) k++;
max = Math.max(max,right-left+1);
}
return max;
}
}
翻了翻题解,发现还有高手!
以下是大佬的题解.
难理解的地方在于为啥能直接返回 right - left.
滑动窗口,极简代码,新增图解
// 1
class Solution {
public int longestOnes(int[] A, int K) {
int left=0 ,right=0;
while (right<A.length){
if(A[right++]==0) K--;
if(K < 0) {
if(A[left++]==0) K++;
}
}
return right-left;
}
}
// 2
class Solution {
public int longestOnes(int[] A, int K) {
int left=0 ,right=0;
while (right<A.length){
K -= A[right++]^1;
if(K < 0) K += A[left++]^1;
}
return right-left;
}
}
作者:怕冷的三十三
链接:https://leetcode.cn/problems/max-consecutive-ones-iii/solutions/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
将x减到0的最小操作数
刚开始看到题目确实感觉难以下手,"移除数组 nums 最左边或最右边的元素"这是什么鬼???
后来想了想,我们只需要让 数组中剩下的元素 = 原始数组中全部元素的和 - x . 就行了.
这道题目也就变成了寻找能满足上面的条件时,数组的最大值.
找到这个最大值然后让 数组的原始长度 - 最大值,就可以得到最小操作数啦~
但是有个坑(踩了两次qwq)
- 当 原始数组中全部元素的和 - x == 0 时,说明此时不需要进行任何操作(因为元素都是正数),直接返回数组长度就OK
- 当 原始数组中全部元素的和 - x < 0 时,说明把数组中的所有元素凑起来也凑不出x.此时直接返回-1.
错了好几次,通过不断地画图,分析,总算是做出来了~
代码:
class Solution {
public int minOperations(int[] nums, int x) {
int max = 0;
int left = 0;
int right = 0;
int sum = 0;
int sum2 = 0;
for (int i = 0; i < nums.length; i++) sum2 += nums[i];
sum2 -= x;
if (sum2 < 0) return -1;
if (sum2 == 0) return nums.length;
while (right < nums.length) {
if (sum < sum2) {
sum += nums[right++];
}
while (sum > sum2) {
sum -= nums[left++];
}
if (sum == sum2) {
max = Math.max(max, right - left);
sum -= nums[left++];
}
}
return max == 0 ? -1 : nums.length - max;
}
}
看了题解,跟我写的差不多,但是我写的还有可以优化的地方.
优化后:
class Solution {
public int minOperations(int[] nums, int x) {
int max = 0;
int left = 0;
int right = 0;
int sum = 0;
int sum2 = 0;
for (int i = 0; i < nums.length; i++) sum2 += nums[i];
sum2 -= x;
if (sum2 < 0) return -1;
if (sum2 == 0) return nums.length;
while (right < nums.length) {
sum += nums[right++];
while (sum > sum2)
sum -= nums[left++];
if (sum == sum2)
max = Math.max(max, right - left);
}
return max == 0 ? -1 : nums.length - max;
}
}
水果成篮
emmm,太久没用HashMap了,都快不会用了.
class Solution {
public int totalFruit(int[] fruits) {
int max = 0;
HashMap<Integer,Integer> hashMap = new HashMap<>();
for(int left=0,right=0; right < fruits.length;right++) {
hashMap.put(fruits[right],hashMap.getOrDefault(fruits[right],0)+1);
while(hashMap.size() > 2) {
hashMap.put(fruits[left],hashMap.get(fruits[left])-1);
if(hashMap.get(fruits[left]) == 0)
hashMap.remove(fruits[left]);
left++;
}
max = Math.max(max,right-left+1);
}
return max;
}
}
还可以不使用HashMap,因为它给出了
0 <= fruits[i] < fruits.length
可以使用数组来模拟.
多定义了一个 kinds 变量,用来统计水果的种类.
public int totalFruit2(int[] fruits) {
int max = 0;
int n = fruits.length;
int[] hashMap = new int[n + 1];
for (int left = 0, right = 0, kinds = 0; right < fruits.length; right++) {
int in = fruits[right];
if (hashMap[in] == 0) kinds++;
hashMap[in]++;
while (kinds > 2) {
int out = fruits[left];
hashMap[out]--;
if (hashMap[out] == 0)
kinds--;
left++;
}
max = Math.max(max, right - left + 1);
}
return max;
}
找到字符串中所有字母异位词
没写出来,最开始是想用两个hashMap,后来写着写着,又改成使用一个hashMap一个数组模拟.再然后又改成一个数组模拟,emmm改来改去,把自己绕晕了~
看了看题解,发现没有我想的那么复杂.
最开始想的是从窗口里一个一个对比P中出现的字符.搞了半天,没写出来.
其实只需要将窗口的长度固定下来就OK.
- 入窗口,hash[in]++.
- 判断一下right-left+1 是否大于 p字符串的长度.
- 大于,出窗口
- 判断是否要更新结果(right - left + 1 == p字符串的长度)
- 相等,比较两个hash表中的字符出现次数.如果完全一致,那就更新结果.
也有一些小坑:
- 字符串s的长度必须要大于字符串p的长度,不然不可能有结果.
- 如果使用数组模拟hash表,并且设置数组大小为26.那么别忘了要
-'a'
.
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
int n1 = s.length(), n2 = p.length();
if (n1 < n2)
return list;
char[] ss = s.toCharArray();
int[] hash = new int[26];
int[] hashP = new int[26];
for (int i = 0; i < n2; i++) {
hashP[p.charAt(i) - 'a']++;
}
for (int left = 0, right = 0; right < n1; right++) {
char in = ss[right];
hash[in - 'a']++;
if (right - left + 1 > n2) {
char out = ss[left++];
hash[out - 'a']--;
}
if (right - left + 1 == n2) {
int i = 0;
for (; i < 26; i++) {
if (hash[i] != hashP[i]) {
break;
}
}
if (i >= 26) {
list.add(left);
}
}
}
return list;
}
}
在以上代码的基础上做一下优化~
上述代码中,在检查两个hash表中的内容是否相等时,用的方法不是很好.
这道题给出了s串和p串中只有小写字母.由于小写字母只有26个,所以检查起来很快.
但是接下来还有一道题目,让你判断两个字符串,如果还是用上面的方法,那就会超时~
具体的优化思路:
维护一个count,count用来记录"有效字符个数".
具体一点:
- 进窗口: 如果hash[in] <= hashP[in],那么count++.
- 出窗口: 如果hash[out] <= hashP[out],那么count–;
- 更新结果: 如果count == p串长度,更新结果.
坑:
- 注意count维护的时机.
优化后:
class Solution {
public List<Integer> findAnagrams2(String s, String p) {
List<Integer> list = new ArrayList<>();
int n1 = s.length(), n2 = p.length();
if (n1 < n2)
return list;
char[] ss = s.toCharArray();
int[] hash = new int[26];
int[] hashP = new int[26];
for (int i = 0; i < n2; i++) {
hashP[p.charAt(i) - 'a']++;
}
int count = 0;
for (int left = 0, right = 0; right < n1; right++) {
int in = ss[right] - 'a';
hash[in]++;
if (hash[in] <= hashP[in]) count++;
if (right - left + 1 > n2) {
int out = ss[left++] - 'a';
if (hash[out] <= hashP[out]) count--;
hash[out]--;
}
if (count == n2) {
list.add(left);
}
}
return list;
}
}
串联所有单词的子串
尝试了两次,没写出来.
- 想不到,可以这样写.
- String/StringBuilder 中有的方法不知道,而且用的不熟练.比如说substring.
- 在出窗口这里卡了一下.
看完题解后,自己写出来的代码:
count 的位置和加减容易写错.
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> list = new ArrayList<>();
int lenS = s.length();
int lenWords = words.length;
int lenWord = words[0].length();
HashMap<String, Integer> hashWords = new HashMap<>();
for (String str : words)
hashWords.put(str, hashWords.getOrDefault(str, 0) + 1);
for (int i = 0; i < lenWord; i++) {
HashMap<String, Integer> hashS = new HashMap<>();
int count = 0;
for (int left = i, right = i + lenWord; right <= lenS; right += lenWord) {
String in = s.substring(right - lenWord, right);
hashS.put(in, hashS.getOrDefault(in, 0) + 1);
if (hashS.get(in) <= hashWords.getOrDefault(in, 0))
count++;
if (right - left > lenWord * lenWords) {
String out = s.substring(left, left + lenWord);
if (hashS.get(out) <= hashWords.getOrDefault(out, 0))
count--;
hashS.put(out, hashS.get(out) - 1);
left += lenWord;
}
if (count == lenWords)
list.add(left);
}
}
return list;
}
题解代码:
class Solution {
public List<Integer> findSubstring(String s, String[] words) {
List<Integer> ret = new ArrayList<>();//返回答案
HashMap<String, Integer> hash1 = new HashMap<>();//hash1用来记录words中出现过的单词以及对应次数
for (String str : words) {
hash1.put(str, hash1.getOrDefault(str, 0) + 1);
}
int len = s.length();
int wordLength = words[0].length();
int wordsLength = words.length;
for (int i = 0; i < wordLength; i++) {
HashMap<String, Integer> hash2 = new HashMap<>();
for (int right = i, left = i, count = 0; right + wordLength <= len; right += wordLength) {
String str = s.substring(right, right + wordLength);
hash2.put(str, hash2.getOrDefault(str, 0) + 1);
if (hash2.get(str) <= hash1.getOrDefault(str, 0)) {
count++;
}
if (right - left + 1 > wordLength * wordsLength) {
String str2 = s.substring(left, left + wordLength);
if (hash2.get(str2) <= hash1.getOrDefault(str2, 0)) {
count--;
}
hash2.put(str2,hash2.get(str2)-1);
left += wordLength;
}
if(count == wordsLength) {
ret.add(left);
}
}
}
return ret;
}
}
最小覆盖子串
emmm,怎么说呢,没写出来,但是看完题解,发现我的代码只需要把一个if改成while就能过了,而且速度很快…
class Solution {
public String minWindow(String ss, String tt) {
int min = Integer.MAX_VALUE;
char[] s = ss.toCharArray();
char[] t = tt.toCharArray();
int len = tt.length();
StringBuilder ret = new StringBuilder();
int count = 0, left1 = 0, right1 = 0;
int[] hashS = new int[58];
int[] hashT = new int[58];
for (char ch : t) hashT[ch - 'A']++;
for (int right = 0, left = 0; right < s.length; right++) {
int in = s[right] - 'A';
hashS[in]++;
if (hashS[in] <= hashT[in]) {
count++;
}
while (count == len) {
if (min > right - left + 1) {
right1 = right;
left1 = left;
min = right - left + 1;
}
int out = s[left++] - 'A';
if (hashS[out] <= hashT[out])
count--;
hashS[out]--;
}
}
while (left1 <= right1) {
ret.append(s[left1++]);
}
return min == Integer.MAX_VALUE ? "" : ret.toString();
}
}
总结
- 感觉滑动窗口本质上还是双指针,只不过是同向双指针.
- 使用滑动窗口时要用到单调性.
- 使用滑动窗口的套路就是:进窗口,出窗口,根据题意找个地方更新结果.
- 对于一些需要记录 单词出现次数/种类 的题目,可以定义一个count来优化代码.
本文到这里就结束啦~