字符串包含
###题目描述 给定两个分别由字母组成的字符串A(长度为m)和字符串B(长度为n),字符串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。
解法
轮询
对于字符串B中的每一个字母,是否能在字符串A中找到
显而易见时间复杂度为O(m*n)
改进分析:字符串A、B中可能存在重复字符,且遍历了字符串A之后,除了当前要找的字母外,其他字母其实也找过了,但是没有使用到这些信息。
先排序,后比较
按字母顺序排序后,按顺序线性扫描字符串A中的每一种字母,同时按顺序扫描字符串B中找是否有同样大小的字母。
先排序,快速排序时间复杂度分别为O(m log m)和(n log n),排好序后线性扫描分别需要O(m)和O(n)的时间
改进分析:排序后减少了无用的线性扫描,但题目只关心字母是否出现过,而不关心同种字母有多少个,所以排序过程中有多余的操作
计数排序
通过计数排序,可以得到每一种字母出现的次数。
先排序,时间复杂度分别为O(m)和(n),排好序后需要O(1)的时间(26个字母),辅助空间为长度为26的int数组2个,即 2642 bit
改进分析:题目只关心字母是否出现过,而不关心同种字母有多少个,可考虑用hashtable记录字母是否出现过即可。
标志位
将每个字母是否出现视作开关0 1,那么可以用26bit来表示26个字母是否出现过。
同理,但只需要2个长度为32bit的int32来表示,即 32*2 bit。对这两个int32做
改进分析:对字符串A每个字母进行位运算后得到hashA,不需要完全对字符串B每个字母进行位运算后得到hashB后再比较,而是当串B中的每个字母进行位运算时,就立即与hashA进行位与运算,结果为0则表示该字母在串A中不存在,立即返回false,节约了之后的计算。由此可得最小时间为O(m+1),最大时间为O(m+n)。
// “最好的方法”,时间复杂度O(n + m),空间复杂度O(1)
bool StringContain(string a, string b)
{
int hash = 0;
// 需要先将字符串A中的字母信息按位写入
for (int i = 0; i < a.Length; ++i)
{
// 1左移n次,n为0~25 代表26个字母,如'C'-> 2, 则1左移2次,得到110(二进制表示)
hash |= (1 << (a[i] - 'A'));
}
for (int i = 0; i < b.Length; ++i)
{
if ((hash & (1 << (b[i] - 'A'))) == 0)
{
return false;
}
}
return true;
}