本文参考了文章《字符串包含》
题目描述
给定两个分别由字母组成的字符串A和字符串B,字符串B的长度比字符串A短。请问,如何最快地判断字符串B中所有字母是否都在字符串A里?
为了简单起见,我们规定输入的字符串只包含大写英文字母,请实现函数bool StringContains(string &A, string &B)
比如,如果是下面两个字符串:
String 1:ABCD
String 2:BAD
答案是true,即String2里的字母在String1里也都有,或者说String2是String1的真子集。
如果是下面两个字符串:
String 1:ABCD
String 2:BCE
答案是false,因为字符串String2里的E字母不在字符串String1里。
同时,如果string1:ABCD,string 2:AA,同样返回true。
这里只介绍两种较好地方法:
解法1:
如果允许排序的话,我们可以考虑下排序。比如可先对这两个字符串的字母进行排序,然后再同时对两个字串依次轮询。两个字串的排序需要(常规情况)O(m log m) + O(n log n)次操作,之后的线性扫描需要O(m+n)次操作。
关于排序方法,可采用最常用的快速排序,参考代码如下(在mac下成功运行):
void swap(char *s,long i,long j)//字符串的里面的单个字符交换函数
{
char temp=*(s+i);//使用指针操作
*(s+i)=*(s+j);
*(s+j)=temp;
}
/**
* 将传入的字符数组s根据选取的主元来分成三部分,参考《算法导论》96页
*
* @param s 传入的字符数组指针
* @param lo 需要处理的字符数组首下标
* @param hi 需要处理的字符数组的末下标
*
* @return 主元x最后所在的下标
*/
unsigned long parttion(char *s,long lo,long hi)//hi传入的是最后一个字符串(不是'\0')对应的下标的数字
{
char key;
key=*(s+hi);//选取*(s+hi)为主元
long i=lo-1;
for (long j=lo; j<hi; j++) {
if (s[j]<=key) {
i++;
swap(s,i,j);
}
}
swap(s,i+1,hi);//最后还要将主元交换一次
return i+1;
}
void quickSort(char *s,long lo,long hi)
{
if (lo<hi) {
long k=parttion(s,lo,hi);
quickSort(s,lo,k-1);
quickSort(s,k+1,hi);
}
}
bool compareString(char *longs,char *shorts)
{
int i=0;
for (; i<strlen(shorts); i++)
{
if (i>0)
{
if (*(shorts+i)==*(shorts+i-1))
{
continue;
}
}
for (int j=0; j<strlen(longs); j++)
{
if (j>0)
{
if (*(longs+j)==*(longs+j-1))
{
continue;
}
}
if (*(longs+j)!=*(shorts+i))
{
if (j==strlen(longs)-1)
{
return false;
}
continue;
}
break;
}
}
return true;
}
int main(int argc, const char * argv[])
{
char mainstatus;
char s1[]="28713564";
char s2[]="22879";
quickSort(s1, 0, strlen(s1)-1);
quickSort(s2, 0, strlen(s2)-1);
mainstatus = compareString(s1,s2);
if (mainstatus)
{
printf("true\n");
} else {
printf("false\n");
}
return 0;
}
解法2:
上述方案中,较好的方法是先对字符串进行排序,然后再线性扫描,总的时间复杂度已经优化到了:O(m+n),貌似到了极限,还有没有更好的办法列?
我们可以对短字串进行轮询(此思路的叙述可能与网上的一些叙述有出入,因为我们最好是应该把短的先存储,那样,会降低题目的时间复杂度),把其中的每个字母都放入一个Hashtable里(我们始终设m为短字符串的长度,那么此项操作成本是O(m)或8次操作)。然后轮询长字符串,在Hashtable里查询短字符串的每个字符,看能否找到。如果找不到,说明没有匹配成功,轮询长字符串将消耗掉16次操作,这样两项操作加起来一共只有8+16=24次。
当然,理想情况是如果长字串的前缀就为短字串,只需消耗8次操作,这样总共只需8+8=16次。
参考代码如下:
#include<stdio.h>
#include<string.h>
int main()
{
char *str1="abcdefghijklmnopqrs";
char *str2="cdgft";
int hash[26]={0};//自己定义的哈希表,因为有26个字母,所以是26!
int num=0;//计算哈希表中1的个数
for(int j=0;j < strlen(str2);j++)
{
int index=str2[j]-'a';
if(hash[index]==0)
{
hash[index]=1;
num++;
}
}
for(int k=0;k<strlen(str1);k++)
{
int index=str1[k]-'a';
if(hash[index]==1)
{
hash[index]=0;
num--;
if(num==0)
break;
}
}
if(num==0)
printf("true\n");
else
printf("false\n");
return 0;
}