字符串的排列

题目描述:

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

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

示例1:

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

示例2:

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

注意:

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


解答:

由于字符串的长度在[1,1000],所以如果考虑s1的排列情况,那实在太多了,用s2去对比s1的排列这个思路不太可行。

比较可行的思路是,可以计算s1的长度并统计s1串中每个字母的出现次数,然后去比较s2串中相同长度内字母出现次数是否相同。这里其实用到了滑动窗口的思想。

根据以上思路个人实现的代码如下:

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        int m1[256]={0};  //存放s1串中不同字母的个数
        int m2[256]={0};  //存放s2串中不同字母的个数
        int flag=0;       //判断是否包含的标志位
        
       if(s2.size()<s1.size())  //当s2字符串比s1字符串短时
           return false;        //直接返回false
        else{
              for(int i=0;i<s1.size();i++)  //通过循环统计s1串中的字母个数
                m1[s1[i]]++; //将字母的ASCII码值作为数组下标,遇到一次就加1

              for(int j=0;j<s2.size()-s1.size()+1;j++){ //外层循环为每一个滑动窗格的起始元 
                                                        素,循环次数为s2的长度-s1的长度再加1
                 memset(m2,0, sizeof(m2));  //每次循环前,将记录s2排列的数组清零
                 for(int k=j;k<s1.size()+j;k++){  //用此循环来统计每一个滑动窗口内出现的字母 
                                                    个数
                   m2[s2[k]]++;   //将字母的ASCII码值作为数组下标,遇到一次就加1
              }
           
              for(int n=0;n<256;n++){  //对数组进行遍历
                if (m1[n] == 0 && m2[n] == 0) //当都是零的时候,说明两个字符串中均没有该字母
					continue; //继续遍历
			    else if (m1[n]!=0 && m2[n]!=0 && m1[n] == m2[n]) { //当都不是零且数值相同 
                                                时,说明两个字符串中该字母出现的个数相同
					flag = 0;  //标志位为0
					continue;  //继续遍历
				}
				else   //说明遍历过程中,出现了某个字母出现次数不一样的情况
                {
                    flag = 1;  //标志位变为1
                    break;     //直接跳出此次遍历
                }
			  
            }
            if(flag==0)  //标志位为0的时候,说明在遍历时字符串中字母出现的次数均相同
                  return true;     //包含相同的排列,返回true
          }
            return false; //没有返回true,则返回false
                        
        }
    }
       
};

以上代码虽然提交通过,但是运行时间为204ms,比较慢,仅仅战胜了19%的cpp提交记录。


改进:

所以之后又参考了leetcode官网上一些其他比较好的回答,发现可以从以下几个方面进行改进:

  • 利用字典,减少存储空间,同时不需要遍历进行比较;
  • 以s1字符串的长度,作为循环次数,同时进行统计,而不是分成两次分别去统计s1和s2;
  • 边比较边修改频率,不需要每次清零重新统计;

在进行以上方面的改进后,代码如下:

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        vector <int> m1(26,0);  //构建s1串的字典
        vector <int> m2(26,0);  //构建s2串的字典
        
        if(s1.size()>s2.size()) //判断边界情况
            return false;  
        
        int windowsize=s1.size(); //计算滑动窗口的大小
        for(int i=0;i<windowsize;i++){ //统计初始滑动窗口中字母的排列
            m1[s1[i]-'a']++;  
            m2[s2[i]-'a']++;  
        }
        
        for(int i=windowsize;i<s2.size();i++){ //循环起始值为滑动窗格内的最后一位,最大值为s2 
                                                  的长度
            if(m1==m2)
                return true;
            m2[s2[i-windowsize]-'a']--;  //滑出去的字母个数要减1
            m2[s2[i]-'a']++;             //滑进来的字母个数要加1
        }
        return m1==m2;   //在上述循环条件下,少比较一次,所以增加一次判断
    }
};

以上代码的执行时间明显就快了很多了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值