文章目录
- [1. 长度最小的子数组(LC209)](#1. 长度最小的子数组(LC209))
- [2. 无重复字符的最长子串(LC3)](#2. 无重复字符的最长子串(LC3))
- [3. 最大连续1的个数(LC1004)](#3. 最大连续1的个数(LC1004))
- [4. 将x减到0的最小操作数(LC1658)](#4. 将x减到0的最小操作数(LC1658))
- [5. 水果成篮(LC904)](#5. 水果成篮(LC904))
- [6. 找到字符串中所有字母异位词(LC438)](#6. 找到字符串中所有字母异位词(LC438))
- [7. 串联所有单词的子串(LC30)](#7. 串联所有单词的子串(LC30))
- [8. 最小覆盖子串(LC76)](#8. 最小覆盖子串(LC76))
1. 长度最小的子数组(LC209)
题目描述

解题思路
- 初始化两个指针
left和right,充当窗口的左端点和右端点。循环以下两步 - 进窗口:窗口需要满足和大于等于
sum,如果和小于sum则进窗口 - 判断 -> 出窗口 : 如果和大于等于
sum则更新结果len,出窗口,left向右走一步
注意: 由于数组元素都是正整数,利用单调性,子数组的元素越多,和越大。因此找到符合要求的窗口以后,没必要让右端继续扩大,只需要判断左端缩小以后能否符合要求。这里利用单调性减少了很多没必要的枚举。
时间复杂度:O(n)右指针遍历一遍,左指针不回退。
代码实现
java
public int minSubArrayLen(int target, int[] nums) {
int left = 0;
int right = 0;
int len = Integer.MAX_VALUE;
int sum = 0;
while(right<nums.length){
//进窗口
sum+=nums[right];
//判断
while(sum>=target){
//更新结果
len = Math.min(len,right - left +1);
//出窗口
sum-=nums[left++];
}
right++;
}
return len == Integer.MAX_VALUE ? 0 : len;
}
注意:
- 因为每次len要取最小值,为了防止0干扰结果,初始化要置为最大值。
- 结果可能为0,为了防止返回最大值,返回时需要判断。
2. 无重复字符的最长子串(LC3)
题目描述

解题思路
- 思路一:暴力枚举+
set判断重复。 - 思路二:在思路一上进行优化。定义
left和right充当窗口的左端点和右端点。- 固定左端点,右端点逐步向右移动,窗口每添加一个字符,则加入到
hashset中。 - 当
right遇到重复字符,left只需要移动到重复字符的下一个位置,right不需要回退。

- 固定左端点,右端点逐步向右移动,窗口每添加一个字符,则加入到
代码实现
java
public int lengthOfLongestSubstring(String s) {
HashSet<Character> set = new HashSet<>();
int len = 0;
int left = 0;
int right = 0;
while(right<s.length()){
//判断
while(set.contains(s.charAt(right)))
set.remove(s.charAt(left++));
//进窗口
set.add(s.charAt(right));
len =Math.max(len,right - left +1);
right++;
}
return len;
}
注意: 不可以先添加到哈希表中再判断是否重复
3. 最大连续1的个数(LC1004)
题目描述

解题思路
鉴于题目没有要求翻转0,翻转0的操作比较复杂,这道题的要求可以转换为在给定的数组找到最长子数组,并且保证子数组中0的个数不超过K个。
- 定义
left=0,right=0 - 进窗口:遇到1不必理会;遇到0,开始计数
- 判断是否出窗口:当窗口中0个数超过K,
left向右移动。如果left遇到0,计数器-1,直到窗口合法。 - 更新结果,取最大值
代码实现
java
public int longestOnes(int[] nums, int k) {
int zero = 0;
int len = 0;
for(int left = 0, right = 0;right < nums.length;right++){
//进窗口
if(nums[right]==0)
zero++;
while(zero > k){
if(nums[left]==0)
zero--;
left++;
}
len = Math.max(len,right - left +1);
}
return len;
}
4. 将x减到0的最小操作数(LC1658)
题目描述

解题思路
在两端操作比较复杂,采用"正难则反"的策略:先计算整个数组的和sum,在数组中间找最长子数组,使中间子数组的和为sum-x,剩余两端的元素个数就是最小操作数。这样土题目转换为找到最长的和为target的子数组。(target = sum-x)
题目提醒所有元素都是正数,利用单调性,窗口变大,和增大。因此要维持窗口和sum1小于等于target;当sum1大于target,就没必要扩大窗口,而应该缩小窗口。
- 计算数组和
sum,定义left=0,right=0,定义子数组和sum1 = 0 - 进窗口:
right向右走一步; - 判断是否出窗口:如果
sum1大于target则left向右走一步;相等则记录长度,取最大值。
代码实现
java
public int minOperations(int[] nums, int x) {
int sum = 0;
int n = nums.length;
for(int i :nums )
sum += i;
int target = sum - x;
if(target <0)
return -1;
int sum1 =0;
int left = 0;
int right = 0;
int len = 0;
while(right < n){
sum1 += nums[right];
while(sum1>target){
sum1-=nums[left++];
}
if(sum1 == target)
len = Math.max(len,right - left +1);
right++;
}
return len==0?-1:n-len;
}
5. 水果成篮(LC904)
题目描述

解题思路
翻译:在数组中找到最长子数组,其中最多有两个不同的数。
- 固定
left,向右移动right,当种类超过2时,right停止移动。接下来进行枚举,left向右移动一步,种类可能会变为2,也有可能不变。无论哪种情况right都不需要回退。利用hashmap记录数字和出现的次数。 - 进窗口:
right向右走一步 - 判断是否出窗口:当
hashmap长度大于二时,left要向右移动,出窗口,对应数字的数量要-1。直到hashmap长度回到2 - 更新子数组长度,取最大值。
代码实现
java
public int totalFruit(int[] fruits) {
int len = 0;
int left = 0;
int right = 0;
HashMap<Integer,Integer> map = new HashMap<>();
while(right < fruits.length){
//进窗口
int in = fruits[right];
map.put( in, map.getOrDefault(in,0)+1);
//判断
while(map.size() > 2){
int out = fruits[left];
//出窗口
if(map.get(out) > 1)
map.put(out,map.get(out)-1);
else
map.remove(out);
left++;
}
len = Math.max(len,right - left + 1);
right++;
}
return len;
}
6. 找到字符串中所有字母异位词(LC438)
题目描述

解题思路
- 判断异位词:
- 思路一:对两个字符串都进行排序,再用双指针依次判断是否相等。时间复杂度太高
- 思路二:利用
hashmap统计每个字符串中字母种类和个数,不关心顺序。- 因为字符只有26个,这里
hashmap可以简化为数组hash1[],下标0代表a,以此类推,数组中存的值代表出现的次数。
- 因为字符只有26个,这里
- 主问题:
left和right充当窗口的左端点和右端点- 进窗口:
right向右走一步,hash[in]++ - 判断是否出窗口:只需要判断窗口大小是否小于等于p字符串p长度。不等则出窗口,
hash[out]-- - 更新结果:每个窗口是
hash2,与hash1对比,完全相等就可以确认为异位词。记录当前left下标。
- 进窗口:
代码实现
java
public List<Integer> findAnagrams(String s, String p) {
int[] hash1 = new int[26];
int[] hash2 = new int[26];
int left = 0;
int right = 0;
List<Integer> ret = new ArrayList<>();
//计算p的hash数组
for (int i = 0; i < p.length(); i++) {
hash1[p.charAt(i) - 97]++;
}
while (right < s.length()) {
//进窗口
char in = s.charAt(right);
hash2[in - 97]++;
//判断 出窗口
if (right - left + 1 > p.length()) {
char out = s.charAt(left);
hash2[out - 97]--;
left++;
}
if (right - left + 1 == p.length()) {
//更新结果
boolean equal = true;
for (int i = 0; i < 26; i++) {
if (hash1[i] != hash2[i]) {
equal = false;
break;
}
}
if (equal)
ret.add(left);
}
right++;
}
return ret;
}
7. 串联所有单词的子串(LC30)
题目描述

解题思路

把主串按单词长度分开,就可以转换为找words的异位词。
与上一题的区别:
- 哈希表的映射关系:单词字符串和出现的次数
right每次移动的步长:单词的长度- 以下图为例,无法确定单词恰好对应下标
[0 1 2],也可能存在[1 2 3或[2 3 4 ]这样的组合。因此滑动窗口不止要遍历一次,要遍历单词长度len次。

代码实现
java
public List<Integer> findSubstring(String s, String[] words) {
int sLen = s.length();
int wordLen = words[0].length();
int wordsLen = words.length;
//串联子串的长度
int len = wordLen*wordsLen;
List<Integer> ret = new ArrayList<>();
HashMap<String,Integer> hash1 = new HashMap<>();
for(String ss : words){
hash1.put(ss,hash1.getOrDefault(ss,0)+1);
}
//滑动窗口执行次数
for(int i = 0 ; i < wordLen ; i++){
HashMap<String,Integer> hash2 = new HashMap<>();
for(int left = i,right = i ; right + wordLen <= sLen ; right+=wordLen){
//进窗口
String in = s.substring(right , right + wordLen);
hash2.put(in,hash2.getOrDefault(in,0)+1);
//长度超出串联子串长度,出窗口
if(right - left + 1 > len){
String out = s.substring(left,left+wordLen);
if(hash2.get(out)>1)
hash2.put(out,hash2.get(out)-1);
else
hash2.remove(out);
left+=wordLen;
}
//长度相等时判断两个哈希表是否相等
if(hash1.size()==hash2.size()){
boolean equal = true;
for(String ss : hash2.keySet()){
if(hash1.get(ss)==null || !hash1.get(ss).equals(hash2.get(ss))){
equal = false;
break;
}
}
if(equal)
ret.add(left);
}
}
}
return ret;
}
8. 最小覆盖子串(LC76)
题目描述

解题思路
利用hashmap记录t中字符和出现的次数,这里hashmap简化为数组,下标表示字母,数组中存的值表示出现次数。
- 进窗口:当
right遇到有效字符(该字符在窗口出现次数==t中对应的次数)时停止移动。 - 判断 出窗口:当left遇到有效字符时,如果窗口符合条件则记录窗口和长度,取最小值更新。
left再向后走一步
优化:如果采用遍历判断两个hash表是否相等会非常费时间,引入变量count来标记有效字符的种类。
- 进窗口后,如果两个哈希表中对应数量相同,则
count++,表示这个字符的数量足够了。 - 出窗口之前,如果数量相等,则
count--。 - 在判断两个哈希表是否相等时,先判断
count与t中种类是否相等。
代码实现
java
public String minWindow(String ss, String tt) {
int left = 0;
int right = 0;
int count = 0;//s中有效字符个数
int kinds = 0;//t中字符种类
int len = Integer.MAX_VALUE;//比较合法窗口的长度
int begin = -1;//结果的起始下标
char[] s = ss.toCharArray();
char[] t = tt.toCharArray();
int[] hash1 = new int[128];
int[] hash2 = new int[128];
for(char ch : t){
if(hash1[ch]==0)
kinds++;
hash1[ch]++;
}
while(right<s.length){
//进窗口
char in = s[right];
hash2[in]++;
if(hash1[in]==hash2[in])
count++;
//判断
while(count == kinds){
//更新
int newLen = right - left + 1;
if(newLen<len){
begin = left;
len = newLen;
}
char out = s[left];
if(hash2[out]==hash1[out])
count--;
hash2[out]--;
left++;
}
right++;
}
return begin==-1?"":ss.substring(begin,begin+len);
}