滑动窗口算法:
例题1:最小覆盖子串
这里注意一个细节:Integer值的比较一定要用.equals()方法,而不是==
class Solution {
public String minWindow(String s, String t) {
Map<Character,Integer> need=new HashMap<>();
Map<Character,Integer> window=new HashMap<>();
for(int i=0;i<t.length();++i){
need.put(t.charAt(i),need.getOrDefault(t.charAt(i),0)+1);
}
int left=0,right=0;
//valid不是记录t中多少个字符在window里面符合条件了,而是记录t中多少种字符在window里面符合条件了。是“种”
int valid=0;
//记录最小覆盖子串的起始索引及长度
int start=left,len=Integer.MAX_VALUE;
//开始滑动
while(right<s.length()){
char r=s.charAt(right);
++right;
//进行滑动后的数据更新
if(need.containsKey(r)){
window.put(r,window.getOrDefault(r,0)+1);
if(window.get(r).equals(need.get(r))){
++valid;
}
}
//符合条件时,开始滑动左端点
while(valid==need.size()){
//在符合条件的情况中进行筛选
if(right-left<len){
len=right-left;
start=left;
}
//开始滑动
char l=s.charAt(left);
++left;
//进行滑动后的数据更新
if(need.containsKey(l)){
if(window.get(l).equals(need.get(l))){
--valid;
}
window.put(l,window.get(l)-1);
}
}
}
return len==Integer.MAX_VALUE?"":s.substring(start,start+len);
}
}
例题2:字符串的排列
class Solution {
public boolean checkInclusion(String s1, String s2) {
Map<Character,Integer> window=new HashMap<>();
Map<Character,Integer> need=new HashMap<>();
//将p的所有字符放入need
for(int i=0;i<s1.length();++i){
char c=s1.charAt(i);
need.put(c,need.getOrDefault(c,0)+1);
}
int left=0,right=0;
int valid=0;
//开始滑动
while(right<s2.length()){
char r=s2.charAt(right);
++right; //right已经是r所在位置+1了
//进行滑动后的数据更新
if(need.containsKey(r)){
window.put(r,window.getOrDefault(r,0)+1);
if(window.get(r).equals(need.get(r))){
++valid;
}
}
//符合条件时,开始滑动左端点
while(right-left>=s1.length()){
//在符合条件的情况下,进行筛选
if(valid==need.size()){
return true;
}
char l=s2.charAt(left);
++left;
//进行滑动后的数据更新
if(need.containsKey(l)){
if(window.get(l).equals(need.get(l))){
--valid;
}
window.put(l,window.get(l)-1);
}
}
}
return false;
}
}
例题3:找到字符串中所有字母异位词
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result=new ArrayList<>();
Map<Character,Integer> window=new HashMap<>();
Map<Character,Integer> need=new HashMap<>();
//将p的所有字符放入need
for(int i=0;i<p.length();++i){
char c=p.charAt(i);
need.put(c,need.getOrDefault(c,0)+1);
}
int left=0,right=0;
int valid=0;
//开始滑动
while(right<s.length()){
char r=s.charAt(right);
++right; //right已经是r所在位置+1了
//进行滑动后的数据更新
if(need.containsKey(r)){
window.put(r,window.getOrDefault(r,0)+1);
if(window.get(r).equals(need.get(r))){
++valid;
}
}
//符合条件时,开始滑动左端点
while(right-left>=p.length()){
//在符合条件的情况下,进行筛选
if(valid==need.size()){
result.add(left);
}
char l=s.charAt(left);
++left;
//进行滑动后的数据更新
if(need.containsKey(l)){
if(window.get(l).equals(need.get(l))){
--valid;
}
window.put(l,window.get(l)-1);
}
}
}
return result;
}
}
例题4:无重复字符的最长子串
解法1:滑动窗口算法
window存储子串中字符以及对应数量
时间复杂度:O(n)
空间复杂度:O(1)。字符的 ASCII 码范围为 0 ~ 127 ,哈希表 window最多使用 O(128) = O(1)大小的额外空间。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character,Integer> window=new HashMap<>();
int left=0,right=0;
int len=0;
while(right<s.length()){
char r=s.charAt(right);
++right;
window.put(r,window.getOrDefault(r,0)+1);
while(window.get(r)>1){
char l=s.charAt(left);
++left;
window.put(l,window.get(l)-1);
}
if(right-left>len){
len=right-left;
}
}
return len;
}
}
解法2:动态规划算法
map统计各字符最后一次出现的索引位置。也可以不使用map,而是线性搜索。这样时间复杂度就是O(n^2)
时间复杂度:O(n)
空间复杂度:O(n)。
class Solution {
public int lengthOfLongestSubstring(String s) {
if(s.length()==0){
return 0;
}
//dp[i]表示以s.charAt(i)结尾的最长不重复字符子串长度
int[] dp=new int[s.length()];
dp[0]=1;
//map统计各字符最后一次出现的索引位置
Map<Character,Integer> map=new HashMap<>();
map.put(s.charAt(0),0);
//构建过程中得到最大值result
int result=1;
//开始构建dp数组
for(int right=1;right<s.length();++right){
int left = map.getOrDefault(s.charAt(right), -1);
map.put(s.charAt(right), right); // 更新哈希表
if(dp[right-1]<right-left){ //当有重复字符的子串长度-1大于以s.charAt(right-1)为结尾的最长不重复子串长度时,为直接长度+1
dp[right]=dp[right-1]+1;
}else{ //否则,为有重复字符的子串长度-1
dp[right]=right-left;
}
//记录最大值
result = Math.max(result, dp[right]);
}
return result;
}
}
解法3:动态规划算法+状态压缩
map统计各字符最后一次出现的索引位置。也可以不使用map,而是线性搜索。这样时间复杂度就是O(n^2)
时间复杂度:O(n)
空间复杂度:O(1)。字符的 ASCII 码范围为 0 ~ 127 ,哈希表map最多使用 O(128) = O(1)大小的额外空间。
class Solution {
public int lengthOfLongestSubstring(String s) {
//dp[i]表示以s.charAt(i)结尾的最长不重复字符子串长度。其状态压缩后为temp变量
int temp=0;
//map统计各字符最后一次出现的索引位置
Map<Character,Integer> map=new HashMap<>();
//构建过程中得到最大值result
int result=0;
//开始构建dp数组
for(int right=0;right<s.length();++right){
int left = map.getOrDefault(s.charAt(right), -1);
map.put(s.charAt(right), right); // 更新哈希表
if(right-left>temp){
temp=temp+1;
}else{
temp=right-left;
}
//记录最大值
result = Math.max(result, temp);
}
return result;
}
}