学习了滑动窗口,用几道滑动窗口经典问题,加深一下滑动窗口的学习。
最长字串专题
无重复字符的最长字串
LeetCode3:
给定一个字符串s,请你找出其中不含有重复字符的最长字串的长度。
示例1:
输入:s = “abcabcbb”
输出:3
解释:因为无重复字符的最长字串是“abc”,所以其长度为3。
示例2:
输入:s = “bbbbb”
输出:1
解释:因为无重复字符的最长字串是“b”,所以其长度为1。
示例3:
输入:s = “pwwkew”
输出:3
解释:因为无重复字符的最长字串是“wke”,所以其长度为3。请注意,你的答案必须是字串的长度,“pwke”是一个子序列,不是字串。
思路:
使用两个指针表示字符串中的某个字串的左右边界,其中左指针代表着枚举字串的起始位置,右指针表示窗口的边界
在每一步操作中,我们会将左指针向右移动一格,表示开始枚举下一个字符作起始位置,然后不断右移右指针,需要保证这两个指针对应的字串没有重复字符。移动结束后,这个字串就是对应着以左指针开始的,不包含重复字符的最长字串。记录这个字串的长度。
同时,用数据结构hash来判断时候是否有重复的字符,看集合中时候含有这个元素,如果有调整left的位置,如果没有就加入到集合中。
HashMap<Character,Integer> hashMap = new HashMap<>();
int max = 0;
int left = 0;
for (int right = 0; right < s.length(); right++){
//如果集合中有这个元素,调整left的位置
if (hashMap.containsKey(s.charAt(right))){
left = Math.max(left, hashMap.get(s.charAt(right)) + 1);
}
//如果集合中没有这个元素,加入到集合
hashMap.put(s.charAt(right),right);
max = Math.max(max,right - left + 1);
}
return max;
至多包含两个不同字符的最长子串
LeetCode159:
给定一个字符串s,找出至多包含两个不同字符的最长字串t,并返回该字串的长度。
输入:”eceba“
输出:3
解释:t是”ece“,长度为3。
思路:
如果输入的字符串s的长度小于等于2,那么它自己就是至多包含不同字符的最长子串。
right每往右走一步,都要去判断left和right区间字符串种类有没有超过两种
1.如果没有,说明left和right区间的字符串是符合要求的,更新长度max = Math.max(max,right-left)
2.如果超过了两种,则left往右移动,直到区间字符种类不超过2种为止,更新长度max = Math.max(max,right-left)
又该如何去解决区间内字符种类是否超过了2种?
可以用HashMap,key为字符,value为字符所在区间靠近left的那个索引。
1.map.put(s.charAt(right),right),如果s。charAt(right)已存在,则更新他的索引位置,不存在的,就添加进去。
2.判断map的元素是不是等于3个,如果是,就删除索引最小的那个元素。
public static int lengthOfLongestSubstringTwoDistinct(String s){
//长度 <= 2,就返回字符串长度
if (s.length() <= 2){
return s.length();
}
int left = 0;
int right = 0;
int maxLength = 2;
HashMap<Character,Integer> hashMap = new HashMap<>();
while (right < s.length()){
hashMap.put(s.charAt(right),right);
right++;
if (hashMap.size() == 3){
int delIdx = Collections.min(hashMap.values());
hashMap.remove(s.charAt(delIdx));
left = delIdx + 1;
}
maxLength = Math.max(maxLength,right - left);
}
return maxLength;
}
长度最小的子数组
LeetCode209:
给定一个含有n个正整数的数组和一个正整数target。
找出该数组中,满足其总和大于等于target的长度的最小的连续子数组[numsl,numsl+1,...,numsr-1,numsr],并返回其长度。如果不存在符合条件的子数组,返回0。
示例1:
输入:target = 7,nums = [2,3,1,2,4,3]
输出:2
解释:子数组[4,3],是该条件下的长度最小的子数组。
示例2:
输入:target = 4,nums = [1,4,4]
输出:1
示例3:
输入:target = 11,nums = [1,1,1,1,1,1,1,1]
输出:0
思路:
定义两个指针left,right。sum来存储子数组的和。
每次移动,都将nums[right]加入到sum,如果sum大于s,则更新子数组的最小长度,然后将nums[left]从sum中移除,并将left右移,直到sum小于s,同样更新子数组最小长度。每一轮迭代,将right右移。
public static int minSubArrayLen(int[] nums,int target){
int n = nums.length;
int left = 0;
int right = 0;
int sum = 0;
int minSize = Integer.MAX_VALUE;
if (n == 0){
return 0;
}
while (right < n){
sum += nums[right];
//和大于target,移除左边第一个元素,更新最小窗口;
while (sum >= target){
minSize = Math.min(minSize,right - left + 1);
sum -= nums[left];
left++;
}
right++;
}
return minSize == Integer.MAX_VALUE ? 0:minSize;
}
盛水最多的容器
LeetCode11:
给定一个长度为n的整数数组height。有n条垂线,第 i 条线的两个端点(i,0)和(i,height[i])。
找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
示例1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂线代表输入数组[1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)最大值为49。
示例2:
输入:height = [1,1]
输出:1
思路:
两个指针i,j分别指向水槽板的高h[i],h[j],此时水槽面积为s(i,j)。由于可容纳水的高度由两块板较低的一块决定。因此s(i,j) = min(h[i],h[j])*(j-i)
public static int maxArea(int[] height){
int i = 0;
int j = height.length - 1;
int res = 0;
while (i < j){
res = height[i] < height[j] ?
Math.max(res,(j-i)*height[i++]):
Math.max(res,(j-i)*height[j--]);
}
return res;
}
寻找字串异位词(排列)
字符串的排列
LeetCode567:
给你两个字符串s1和s2,写一个函数来判断s2是否包含s1的排列。如果是,返回true;否则,返回false。
换句话说,s1的排列之一是s2的字串。
示例1:
输入:s1 = “ab”,s2 = “eidbaooo”
输出:true
解释:s2包含s1的排列之一(“ba”)
示例2:
输入:s1 = “ab”,s2 = “eidboaoo”
输出:false
思路:
由于排列不会改变字符串中每个字符的个数,所以只有当两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列。
记s1的长度为n,遍历s2中每个长度为n的字串,判断字串和s1中每个字符的个数是否相等,相等说明该字串是s1的一个排列。
使用两个数cnt1和cnt2,cnt1统计s1中各个字符的个数,cnt2统计当前遍历的字串中各个字符的个数。
使用一个长度为n的滑动窗口维护cnt2:滑动窗口每右滑依次,就多统计一次进入窗口的字符,少统计一次离开窗口的字符。判单cnt1和cnt2是否相等,若相等说明s1的排列之一是s2的字串。
public static boolean checkInclusion(String s1,String s2){
int n = s1.length();
int m = s2.length();
if (n > m){
return false;
}
int[] cnt1 = new int[26];
int[] cnt2 = new int[26];
for (int i = 0; i < n; ++i){
++cnt1[s1.charAt(i) - 'a'];
++cnt2[s2.charAt(i) - 'a'];
}
if (Arrays.equals(cnt1,cnt2)){
return true;
}
for (int i = n; i < m; ++i){
++cnt2[s2.charAt(i) - 'a'];
--cnt2[s2.charAt(i - n) - 'a'];
if (Arrays.equals(cnt1,cnt2)){
return true;
}
}
return false;
}
找到字符串中的所有字母异位
LeetCode438:
给定两个字符串s和p,找到s中所有p的异位词的字串,返回这些字串的起始索引。不考虑答案输出的顺序。
异位词指由相同字母重排列形成的字符串(包括相同的字符串)。
示例1:
输入:s = “cbaebabacd”,p = “abc”
输出:[0,6]
解释:起始索引等于0的子串是“cba”,它是“abc”的异位词。
起始索引等于6的子串是“bac”,它是“abc”的异位词。
示例2:
输入:s = “abab”,p = “ab”
输出:[0,1,2]ab
解释:起始索引等于0的子串是“a'b”,它是“ab”的异位词。
起始索引等于1的子串是“ba”,它是“ab”的异位词。
起始索引等于2的子串是“ab”,它是“ab”的异位词。
思路:
因为字符串s的异位词长度一定与字符串p的长度相同,所以可以构造一个与p长度相同的滑动窗口,当滑动窗口的字母数量和字符串每种字母数量相同时,说明当前窗口为字符串p的异位词。
public static List<Integer> findAnagrams(String s,String p){
int sLen = s.length();
int pLen = p.length();
//s长度小于p,就返回空
if (sLen < pLen){
return new ArrayList<Integer>();
}
List<Integer> list = new ArrayList<>();
int[] sCount = new int[26];
int[] pCount = new int[26];
for (int i = 0; i < pLen; ++i){
++sCount[s.charAt(i) - 'a'];
++pCount[s.charAt(i) - 'a'];
}
if (Arrays.equals(sCount,pCount)){
list.add(0);
}
for (int i = 0; i < sLen - pLen; ++i){
--sCount[s.charAt(i) - 'a'];
++sCount[s.charAt(i + pLen) - 'a'];
if (Arrays.equals(sCount,pCount)){
list.add(i + 1);
}
}
return list;
}