模版
学习自labudong的算法模版
public boolean windowModle( String s1, String s2 ) {
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for ( int i = 0; i < s1.length(); i++ ) {
//统计 待匹配字符串的字符
char ch = s1.charAt( i );
need.put( ch, need.getOrDefault( ch, 0 ) + 1 );
}
int left = 0;
int right = 0;
// valid 代表当前成功了多少个字母
int valid = 0;
while ( right < s2.length() ) {
char c = s2.charAt( right );
right++;
// 窗口扩大
if ( need.containsKey( c ) ) {
window.put( c, window.getOrDefault( c, 0 ) + 1 );
// 如果有一个字母的在两个字符串中出现的频率一样,代表这成功匹配字符+1
if ( window.get( c ).equals( need.get( c ) ) ) {
valid++;
}
}
// 判断左侧窗口是否收缩,还是找到了结果直接返回
while ( right - left >= s1.length() ) {
// 在这里判断是否找到了合法的子串
if ( valid == need.size() ) {
return true;
}
char del = s2.charAt( left );
left++;
// 更新窗口
if ( need.containsKey( del ) ) {
// 当前删除的字符影响了匹配,所以valid-1
if ( window.get( del ).equals( need.get( del ) ) ) {
valid--;
}
//更新数据
window.put( del, window.get( del ) - 1 );
}
}
}
return false;
}
76. 最小覆盖子串
要求s将t 中的字符覆盖,且长度最小,本题中是字串,代表字符串必须是字串中连续的一段。所以可以使用滑动窗口先逐步确定s覆盖t的情况,然后缩小窗口再求长度最小的字串。
代码:
public String minWindow( String s, String t ) {
String res = "";
if ( t.length() > s.length() ) {
return res;
}
HashMap<Character, Integer> need = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
for ( int i = 0; i < t.length(); i++ ) {
char ch = t.charAt( i );
need.put( ch, need.getOrDefault( ch, 0 ) + 1 );
}
int l = 0;
int r = 0;
int valid = 0;
int len = Integer.MAX_VALUE;
int start = 0;
while ( r < s.length() ) {
char ch = s.charAt( r );
r++;
if ( need.containsKey( ch ) ) {
window.put( ch, window.getOrDefault( ch, 0 ) + 1 );
if ( window.get( ch ).equals( need.get( ch ) ) ) {
valid++;
}
}
// 判断左侧窗口是否要收缩
while ( valid == need.size() ) {
// 在这里更新最小覆盖子串
if ( r - l < len ) {
start = l;
len = r - l;
}
// d 是将移出窗口的字符
char d = s.charAt( l );
// 左移窗口
l++;
// 进行窗口内数据的一系列更新
if ( need.containsKey( d ) ) {
if ( window.get( d ).equals( need.get( d ) ) ) {
valid--;
}
window.put( d, window.get( d ) - 1 );
}
}
}
return len == Integer.MAX_VALUE ? "" : s.substring( start, start+len );
}
本题考察的算法思想时滑动窗口,对于滑动窗口,需要考虑窗口什么时候扩大、什么时候缩小窗口、什么时候结算窗口内的信息、窗口扩大及缩小的对应操作。
首先遍历t字符串,统计每个字母出现的频率。
定义valid代表已完成匹配字符的数量
r 为窗口右边界,l 代表窗口左边界
开始遍历 s 中的每个字符,
若当前字符在 t 中出现过,window 将这个字符收入,对应词频+1,
若window 与 need 中的这个字符频率相等,valid++,代表完成一个字符的匹配
若某时valid==need.size(),代表已经完成了匹配,需要结算窗口的信息
信息包含左右边界的位置,以及此时最小覆盖的字串长度是多少
r-l 代表此时窗口的大小, r-l < len 代表此时窗口小于上一次的窗口,
此时更新 要返回的子字符串的开始与结束位置
start = l , l 代表窗口左边界,将这种情况下的左边界赋值给 l
len = r-l , len 代表窗口大小
由于题目要求的是最小的包含 t 字符的字符串,所以需要不断缩小窗口来求出最小的窗口大小
need.containsKey( d ) 判断当前 l 位置的字符是否是need需要的,就是t中是否包含的字符
若window 与 need 中的这个字符频率相等,valid--,下一次重新开始匹配
window 需要将这个字符移除
若这个字符不在 need 中,也就是不需要的字符,则继续判断下一个左边界代表的字符。
最后返回之前判断len是否等于 Integer.MAX_VALUE
如果等于,则代表没能匹配成功,
如果不等,代表上面过程中有匹配成功的情况,而在上面过程中,我们总是不断缩小窗口大小,且每次都将左窗口赋值给 start , len代表着完成匹配的窗口大小。
所以最后截取的字符串为 s.substring(start, start + len) 。
此解法仅在匹配过程中线性扫描两个字符串,所以整体时间复杂度为O(n)。
567. 字符串的排列
题目中要求的是是否包含排列,对于排列,不要求其顺序,但要求其各个字符的频率相等,所以可以使用滑动窗口来判断窗口中包含的字符是否与待匹配串的每个字符频率都相等。
同样需要注意是
什么时候扩大窗口
什么时候缩小窗口
什么时候结算窗口内的信息
如何更新窗口内的信息
public boolean checkInclusion( String s1, String s2 ) {
boolean b = false;
int []a1= new int[26];
int []a2= new int[26];
int l=0;
int r=0;
for ( int i = 0; i < s1.length(); i++ ) {
a1[s1.charAt( i )-'a']++;
}
while ( r<s2.length() ){
int index = s2.charAt( r )-'a';
a2[index]++;
if(r-l+1==s1.length()){
if(check(a1,a2)){
return true;
}else{
a2[s2.charAt(l)-'a']--;
l++;
}
}
r++;
}
return b;
}
private boolean check( int[] a1, int[] a2 ) {
Boolean f = true;
for ( int i = 0; i < a1.length; i++ ) {
if(a1[i]!=a2[i])return false;
}
return f;
}