文章目录
LeetCode209.长度最小的子数组
题目链接: leetcode209.长度最小的子数组
题目解析
算法原理
解法一:暴力枚举出所有的子数组的和 O(n^3)
解法二:利用单调性,使用同向双指针来优化. O(n)
同向双指针即是left和right都向同一个方向移动.同向双指针又称为滑动窗口.
优化: 先固定左边的left然后让right指向left,定义一个sum,让right向右移动,用sum存区间之和,当sum >= 7时,然后left++,right重新移到left的位置,再将right移动进行求和,我们发现此步骤很繁琐,所以我们干脆不移动right,将它放在原位置,光让left++,再求区间大小.
我们发现了两个规律:
- 当right移动到合适位置的时候,后面的情况就不用再枚举了.
- 当left向右移动一步之后right是不回退的.
用法:
- left = 0,right = 0.
- 进窗口
- 判断 (出窗口)
这个方法利用了单调性规避了很多没有必要的枚举行为.
编写代码
class Solution {
public int minSubArrayLen(int target, int[] nums)
{
int n = nums.length,sum = 0,len = Integer.MAX_VALUE;
for(int left=0,right=0; right < n; right++)
{
sum += nums[right];//进窗口
while(sum >= target)//判断
{
len = Math.min(len,right - left + 1);//更新结果
sum -= nums[left++];//出窗口
}
}
return len == Integer.MAX_VALUE?0 : len;
}
}
LeetCode3.无重复字符的最长子串
题目链接: leetcode3.无重复字符的最长子串
题目解析
算法原理
解法一: 暴力枚举 + 哈希表(判断字符是否重复出现) O(n^2)
解法二:利用规律,使用"滑动窗口"来解决问题.
- left=0,right=0
- 进窗口:让字符进入哈希表
- 判断, 窗口内出现重复字符进行出窗口(从哈希表中删除该字符)
编写代码
class Solution {
public int lengthOfLongestSubstring(String ss) {
char[] s = ss.toCharArray();
int[] hash = new int[128]; //用数组模拟哈希表
int left = 0,right = 0,n = ss.length();
int ret = 0;
while(right < n)
{
hash[s[right]]++; // 进入窗口
while(hash[s[right]] > 1) // 判断
{
hash[s[left++]]--; // 出窗口
}
ret = Math.max(ret,right - left + 1);//更新结果
right++;
}
return ret;
}
}
LeetCode1004.最大连续1的个数 III
题目链接: leetcode1004.最大连续1的个数 III
题目解析
最多k个0,说明可以翻转的个数是小于等于k
算法原理
在这个数组中,找到一个连续的区域,只要让这个区域满足0的个数不要超过k次就行.
将原始的问题转化为:找出最长的子数组,0的个数不超过k个.
解法一:暴力枚举 + zero 计数器
解法二:滑动窗口
- left=0,right=0
- 进窗口,right指的如果是1,无视,如果是0,计数器+1
- 判断当zero > k时,让left一直右移,直到窗口合法为止.出窗口和进窗口是一样的逻辑.left指的如果是1,无视,如果是0,计数器-1.
编写代码
class Solution {
public int longestOnes(int[] nums, int k) {
int ret = 0;
for(int left = 0,right = 0, zero = 0; right < nums.length; right++)
{
if(nums[right] == 0) zero++;//进窗口
while(zero > k) //判断
{
if(nums[left++] == 0) zero--; //出窗口
}
ret = Math.max(ret,right - left + 1);
}
return ret;
}
}
LeetCode1658.将 x 减到 0 的最小操作数
题目链接: leetcode1658.将 x 减到 0 的最小操作数
题目解析
算法原理
正难则反.
问题转化:找出最长的子数组的长度(len),所有的元素的和正好等于sum - x(target)
解法:滑动窗口
- left=0,right=0
- 进窗口 tmp+= nums[right]
- 判断 tmp > target ,出窗口 tmp-= nums[left],更新结果 sum == target.
编写代码
class Solution {
public int minOperations(int[] nums, int x) {
int sum = 0;
for(int a : nums) sum += a;
int target = sum - x;
//处理细节
if(target < 0) return -1;
int ret = -1;
for(int left = 0, right = 0, tmp = 0; right < nums.length; right++)
{
tmp += nums[right]; // 进窗口
while(tmp > target) // 判断
tmp -= nums[left++]; // 出窗口
if(tmp == target)
ret = Math.max(ret,right - left + 1);// 更新结果
}
if(ret == -1) return ret;
else return nums.length - ret;
}
}
LeetCode904.水果成篮
题目链接: leetcode904.水果成篮
题目解析
将问题转化:找出一个最长的子数组的长度,子数组里面不超过两种类型的水果.
算法原理
解法一: 暴力枚举 + 哈希表
解法二:滑动窗口
- left=0,right=0
- 进窗口,把right所指向的元素放进哈希表里.hash[f[right]]++
- 判断 如果hash.length > 2就出窗口,出窗口(hash[f[left++]]–)
编写代码
class Solution {
public int totalFruit(int[] f) {
Map<Integer,Integer> hash = new HashMap<Integer,Integer>();//统计窗口水果种类
int ret = 0;
for(int left = 0,right = 0; right < f.length; right++)
{
int in = f[right];
hash.put(in,hash.getOrDefault(in,0) + 1);//进窗口
while(hash.size() > 2)
{
int out = f[left];
hash.put(out,hash.get(out) - 1);//出窗口
if(hash.get(out) == 0)
{
hash.remove(out);
}
left++;
}
// 更新结果
ret = Math.max(ret,right - left + 1);
}
return ret;
}
}
class Solution {
public int totalFruit(int[] f) {
int n = f.length;
int[] hash = new int[n +1];
int ret = 0;
for(int left = 0,right = 0,kinds = 0; right < n; right++)
{
int in = f[right];
if(hash[in] == 0)
{
kinds++; //维护水果种类
}
hash[in]++;//进窗口
while(kinds > 2)
{
int out = f[left];
hash[out]--;//出窗口
if(hash[out] == 0)
{
kinds--;
}
left++;
}
// 更新结果
ret = Math.max(ret,right - left + 1);
}
return ret;
}
}
LeetCode438.找到字符串中所有字母异位词
题目链接: leetcode438.找到字符串中所有字母异位词
题目解析
异位词:如下abc acb bac bca cab cba都是p的异位词.
算法原理
1.如何快速判断两个字符串是否是"异位词"?判断每个字符串里的每个字符出现的次数.
我们可以利用哈希表.遍历每个字符,比较两个哈希表对应位置字符出现的个数是否相等.
2.解决问题
解法一:暴力解法
先把p字符串里每个字符出现了多少个放到hash1表里,p这个字符串的长度是m,然后在s字符串里找到所有长度为m的字串
找出来后,把其放进另一个哈希表里,然后比较两个哈希表是否相等即可.
s字符串里找到所有长度为m的字串:
解法二:滑动窗口
-
left=0,right=0
-
进窗口 hash2[in]++
-
判断 当窗口大于m就要移动left即 (right - left + 1 > m),如果这个条件成立就要出窗口一次,hash2[out]–,最后检查hash2和hash1的信息是否一致,然后更新结果.
优化:更新结果的判断条件
进窗口:进入后判断hash2[in] <= hash1[in]如果符合就让count++.
出窗口:出去前判断hash2[out] <= hash1[out]如果符合就让count–
更新结果: count == m.
编写代码
class Solution {
public List<Integer> findAnagrams(String ss, String pp) {
List<Integer> ret = new ArrayList<Integer>();
char[] s = ss.toCharArray();
char[] p = pp.toCharArray();
int[] hash1 = new int[26]; // 统计字符串p中每一个字符出现的个数
for(char ch : p)
{
hash1[ch - 'a']++;
}
int[] hash2 = new int[26];//统计窗口中每个字符出现的个数
int m = p.length;
for(int left = 0, right = 0,count = 0;right < s.length;right++)
{
char in = s[right];
hash2[in - 'a']++;
if(hash2[in - 'a'] <= hash1[in - 'a'])
{
count++;// 进窗口 + 维护count变量
}
if(right - left + 1 > m)
{
char out = s[left++];
if(hash2[out - 'a']-- <= hash1[out - 'a'])
{
count--;//出窗口 + 维护count
}
}
// 更新hash表
if(count == m)
{
ret.add(left);
}
}
return ret;
}
}
LeetCode30.串联所有单词的子串
题目链接: leetcode30.串联所有单词的子串
题目解析
算法原理
我们发现这道题和438题就变成了一个题了.
解法:滑动窗口 + 哈希表
对比之前的算法有不同的点如下:
-
哈希表
-
left和right指针的移动
-
滑动窗口执行的次数
编写代码
class Solution
{
public List < Integer > findSubstring(String s, String[] words)
{
List < Integer > ret = new ArrayList < Integer > ();
// 保存字典中所有单词的频次
Map < String, Integer > hash1 = new HashMap < String, Integer > ();
for(String str: words) hash1.put(str, hash1.getOrDefault(str, 0) + 1);
int len = words[0].length(), m = words.length;
for(int i = 0; i < len; i++) // 执⾏次数
{
// 保存窗⼝内所有单词的频次
Map < String, Integer > hash2 = new HashMap < String, Integer > ();
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++;
// 判断
if(right - left + 1 > len * m)
{
// 出窗⼝ + 维护 count
String out = s.substring(left, left + len);
if(hash2.get(out) <= hash1.getOrDefault(out, 0)) count--;
hash2.put(out, hash2.get(out) - 1);
left += len;
}
// 更新结果
if(count == m) ret.add(left);
}
}
return ret;
}
}