本周刷到力扣567题,字符串的排列,本想着再次用朴素的循环套来解决,但总觉得缺少些什么,那么由本体,给自己引入滑动窗口这种小思想吧
给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。
换句话说,s1 的排列之一是 s2 的 子串 。
示例 1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").
示例 2:输入:s1= "ab" s2 = "eidboaoo"
输出:false
提示:
1 <= s1.length, s2.length <= 104
s1 和 s2 仅包含小写字母来源:力扣(LeetCode)
本题一上来看其实这俩例子我有点懵,乍一看不就是reverse字串一下判断两次,后来发现是自己傻了,原来是需要在被验证字符串内判断验证字符的所有种类全排列。
其实最简单的办法还是常用的全排列for循环,但那样时间复杂度以及空间复杂度都过于高了,最重要的还是感觉学不到东西,得来点新的。
滑动窗口,本质上其实还是for循环,但不过是双指针罢。在本题如何引入滑动窗口,便是难题。
那么我们不妨试试统计字母个数来验证,看到这你
可能会有点懵,为什么,那样子还怎么管全排列?这就是本算法的巧妙之处!
例如本题,先计算需要查找的字符串各字母的个数
计算完毕后,我们单纯拿到被验证字符串内肯定不行,因为我们不知道他在被验证的字符串内的顺序和排列是什么样的,不知道其中有没有其他字母的间隔
所以我们只能利用双指针,来进行单字符串内的字符段窗口滑动(其实相当于变相的双指针截取字符串来统计当前字符串内的各字母个数),一点一点来验证,即可确保被验证数组/字符串内的字符串的排列和顺序是被允许的。
放代码
public class 字符串的排列567 {
public static boolean checkInclusion(String s1, String s2) {
int n = s1.length(), 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) {
/*关于为什么要把i=n,因为为了节省算法时间,毕竟n是要验证的字符串,他最短是多少,那么在被验证的字符串内也就同理需要这些长度,省时省力.*/
++cnt2[s2.charAt(i) - 'a'];
--cnt2[s2.charAt(i - n) - 'a'];
if(s2.charAt(i) == s2.charAt(i-n)){/*自己的想法,循环执行的条件是在没有成功找到需要验证的字符串,那么如果新进的字符和踢出的字符相同,那么直接continue进行下一次
因为有他过不了;*/
continue;
}
/*这部分的sout用于仔细观察滑动窗口如何运行*/
System.out.println(i+"|"+(i-n));/*本sout可不断显示滑动窗口的指针1&2的位置,更加直观*/
// for(int p :cnt2){
// System.out.print(p+"|");//显示判断时指针内部字符串字母的个数;
// }
// System.out.println();
if (Arrays.equals(cnt1, cnt2)) {
return true;
}
}
return false;
}
注释可能会显得比较乱,但是已经最大程度上做到了清晰的解释了。
剩下的就只能小伙伴们自己造诣啦