力扣567. 字符串的排列---滑动窗口与哈希

567. 字符串的排列

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,第一个字符串的排列之一是第二个字符串的 子串 。

示例 1:

输入: s1 = "ab" s2 = "eidbaooo"
输出: True
解释: s2 包含 s1 的排列之一 ("ba").
示例 2:

输入: s1= "ab" s2 = "eidboaoo"
输出: False
 

提示:

输入的字符串只包含小写字母
两个字符串的长度都在 [1, 10,000] 之间

题解:

方法一:双哈希+滑动窗口

由题意可知,我们可以创建两个哈希表分别存储s1和s2的与s1长度相等的子串的每个字母的数量,如果遍历两个哈希表发现相等,则直接返回true;反之则继续寻找另一种子串与s1比较即可,直到查找完毕返回0。

思路是很简单的,但是难在寻找子串的过程,因为子串的可能性极大,若是采用多层for循环必会超时,于是这里我们可以考虑采用滑动窗口思想,即使用一次遍历来完成我们的目标。
力扣3. 无重复字符的最长子串—滑动窗口+哈希表中更加详细的讲解了一下滑动窗口思想,滑动窗口使用的条件是连续性和长度固定且寻找的东西为单一顺序,因此这里略讲一下即可。
滑动窗口即先确定好一段固定的长度,然后每次去掉前面一个,加上后面一个,不断的进行此操作直到结束。
因此这里可以遍历出原子串的基础上的所有的固定长度的连续子串的一种拼法,而由于我们使用哈希存储字母数量,所以一种拼法足够。
这也是为什么滑动窗口经常与哈希思想在一起使用的道理,因为只凭借滑动窗口一次遍历想要完成n次遍历完成的事情还是要付出点别的“代价”的。

代码:

bool same(int*x,int*y)
{
    
    
    for(int i=0;i<26;i++)
    {
    
    
        if(x[i]!=y[i])
            return 0;
    }
    return 1;
}
bool checkInclusion(char * s1, char * s2){
    
    
    int hash1[26] = {
    
    0};
    int hash2[26] = {
    
    0};
    int m = strlen(s1);
    int n = strlen(s2);
    if(m>n)//避免后面越界
        return 0;
    for(int i=0;i<m;i++)
    {
    
    
        hash1[s1[i]-'a']++;
        hash2[s2[i]-'a']++;
    }
    if(same(hash1,hash2))
    //验证形成的子串是否是s1的排列,即验证存储二者的哈希表是否相同
    {
    
    
        return 1;
    }

    for(int i=m;i<n;i++)//因为前面长度已经遍历了
    {
    
    
        hash2[s2[i-m]-'a']--;//左边的去掉,所以在存储字母的哈希表里去掉一个
        hash2[s2[i]-'a']++;//右边的拿过来
        if(same(hash1,hash2))
        {
    
    
            return 1;
        }
    }
    return 0;
}

方法二:单哈希优化+滑动窗口

由于每次都要遍历两个哈希表是否相同是完全没有必要的,因为我们只是出去一个,进来一个,没必要把整个哈希表比完,因此我们可以只使用一个哈希表进行“求同存异”的操作即可,即寻找子串的不同即可。
需要注意的是这里的哈希表是平衡哈希表,即hash[x]==0为代表x所代表的字母是平衡字母,即两个子串都有且个数相同或都没有,若为别的即为不平衡状态。
注意这里的平衡是相对固定不变的s1而言。

代码:

bool checkInclusion(char * s1, char * s2){
    
    
    int hash[26] = {
    
    0};
    int m = strlen(s1);
    int n = strlen(s2);
    if(m>n)
        return 0;
    for(int i=0;i<m;i++)
    {
    
    
        hash[s1[i]-'a']++;//有就加上
        hash[s2[i]-'a']--;//它有就减,相当于一个平衡的操作
    }
    int diff = 0;//开始不同个数为0
    for(int i=0;i<26;i++)
    {
    
    
        if(hash[i]!=0)
            diff++;//遍历找出不同的个数
    }
    if(diff==0)
        return 1;

    for(int i=m;i<n;i++)
    {
    
    
        int x = s2[i-m]-'a';//最左边的
        int y = s2[i]-'a';//最右边的
        if(x==y)//如果出去的和进来的是一样的不用对哈希表进行操作
            continue;
        if(hash[x]==0)
        {
    
    //如果x代表的字母是平衡字母,所以其出去一个后变为不平衡了,由于不知道进来的是不是平衡字母,所以不同的个数先加一
            diff++;
        }
        hash[x]++;//因为出去了不平衡了,所以哈希对应此字母要加一
        if(hash[x]==0)
        {
    
    //即代表进来的也是一个平衡字母,即与s1是平衡的,所以不同的个数减一
            diff--;//所以如果进来的不是平衡字母就不会进行减少不同的个数的操作
        }
        if(hash[y]==0)//因为分步进行,即入和出是分步的,所以我们对于diff的操作是分两次进行的,
//所以即我们对于出来的会先加一次diff,如果进来的和出去的相同我们再让diff--,
//否则diff就不变了,即在刚开始我们就已经将可能造成不同的情况考虑进去了
        {
    
    
            diff++;
        }
        hash[y]--;
        if(hash[y]==0)
        {
    
    
            diff--;
        }
        if(diff==0)
            return 1;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xiangguang_fight/article/details/115444124
今日推荐