对于此类问题 , 我们需要确定一个最大或者最小满足条件的区间。把区间在字符串或者数组上变化移动想象成一个在窗沿滑动的窗口,当滑到满足条件的位置时,这个窗口即为所求区间。
1.无重复字符的最长字串
设置i为滑动窗口的左边界,j为左边界。j向右移动的目的是向区间添加字符,是的区间无重复增长。i的目的是一旦j的元素与子串中的元素重复,i右移直到将重复的元素丢到后面去。
因为要求无重复字串,所以我们用hashset记录窗口内包含的字符。首先使j向右动,hashset检查有无重复字符,如果没有则将s(j)添加进hashset 。如果有重复字符就移动i 去掉重复字符及重复字符之前的字符。 这样维持一个区间(i,j) j-i+1即为无重复字串的长度。
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0, i = 0, j = 0;
Set<Character> set = new HashSet<>();
while (i < n && j < n) {
if (!set.contains(s.charAt(j))) {
set.add(s.charAt(j++));
ans = Math.max(ans, set.size());
} else {
set.remove(s.charAt(i++));
}
}
return ans;
}
最小覆盖子串
与上题相似,同样是找具有特定性质的最短或者最长区间,本题的特定性质是“覆盖”,即s的某个子串中包含t中的所有字母。那么快指针 right的作用是向右移动添加字符,直到【left,right】区间内包含T中的所有元素。那么left的作用就是向右移动,在满足“覆盖”的条件下使区间尽可能变短。
重复上述过程,并且记录最小区间的起始大小。
因为题目中没有要求顺序,并且考虑到T字符串中有字符重复的情况,我们用hashmap<character,integer>来存储字符出现的次数。
public String minWindow(String s, String t) {
int left=0;
int right=0;//区间左右边界
Map<Character,Integer> need=new HashMap<>();//记录t字符中所有字符出现次数
for (int i=0;i<t.length();i++){
int count=need.getOrDefault(t.charAt(i),0);
need.put(t.charAt(i),count+1);
}
Map<Character,Integer> windowCount=new HashMap<>();//windowcount记录滑动窗口内字符出现的次数
int[] ans={s.length()+1,0,0};//记录满足条件子串的长度,起始位置
int format=0;//记录windowcount与need中字符个数相同的个数
while (right<s.length()){
char c=s.charAt(right);
int count=windowCount.getOrDefault(c,0).intValue();
windowCount.put(c,count+1);
if (need.containsKey(c)&&windowCount.get(c).intValue()==need.get(c).intValue())format++;//注意 Interger与Interger之间的比较有坑,不能直接比较。-128-127在缓冲区可以比,超过就不能比了。
while (left<=right&&need.size()==format){
c=s.charAt(left);
if (ans[0]>right-left+1){
ans[0]=right-left+1;
ans[1]=left;
ans[2]=right;
}
windowCount.put(c,windowCount.get(c).intValue()-1);
if (need.containsKey(c)&&windowCount.get(c).intValue()<need.get(c).intValue())format--;
left++;
}
right++;
}
return ans[0]==s.length()+1?"":s.substring(ans[1],ans[2]+1);
}