题目描述:
假设这有一个各种字母组成的字符串A,和另外一个字符串B,字符串里B的字母数相对少一些。什么方法能最快的查出所有小字符串B里的字母在大字符串A里都有?
比如,如果是下面两个字符串:
String 1: ABCDEFGHLMNOPQRS
String 2: DCGSRQPO
答案是true,所有在string2里的字母string1也都有。
如果是下面两个字符串:
String 1:ABCDEFGHLMNOPQRS
String 2: DCGSRQPZ
答案是false,因为第二个字符串里的Z字母不在第一个字符串里。
1:O(n*m)的轮询方法
判断一个字符串是否在另一个字符串中,最直观也是最简单的思路是,针对第二个字符串string2中每一个字符,一一与第一个字符串string1中每个字符依次轮询比较,看它是否在第一个字符串string1中。代码如下:
int CompareString(string LongString, string ShortString)
{
int i,j;
for (i=0; i<ShortString.length(); i++)
{
for (j=0; j<LongString.length(); j++) //O(n*m)
{
if (LongString[j] == ShortString[i])
{
break;
}
}
if (j == LongString.length())
{
cout << "false"<< endl;
return 0;
}
}
cout << "true" <<endl;
return 1;
}
2:O(mlogm)+O(nlogn)+O(m+n)的排序方法
一个稍微好一点的方案是先对这两个字符串的字母进行排序,然后同时对两个字串依次轮询。两个字串的排序需要(采用最常用的快速排序)O(m log m) + O(n log n)次操作,之后的线性扫描需要O(m+n)次操作。代码如下:
void compare(string str1, string str2) //str1 is longstring, str2 is shortstring
{
int posOne = 0;
int posTwo = 0;
while (posTwo < str2.length() && posOne < str1.length())
{
while (str1[posOne] < str2[posTwo]&& posOne < str1.length() - 1)
posOne++;
//如果和str2相等,那就不能动。只有比str2小,才能动。
if (str1[posOne] != str2[posTwo])
break;
//posOne++;
// str1Pos是否自增,取决于str2中是否有重复字符串
posTwo++;
}
if (posTwo == str2.length())
cout << "true" <<endl;
else
cout << "false"<< endl;
}
3:O(n+m)的计数排序方法
同样是先排序,后比较的算法,因为需要排序的元素为26个字母,所以,可以采用线性时间的排序算法,比如计数排序,这样,需要的时间为:O(n) + O(m) +O(n+m) = O(n+m)。代码不再赘述。
4:O(n+m)的hashtable的方法
可以对短字串进行轮询(最好是应该把短的先存储,那样,会降低题目的时间复杂度),把其中的每个字母都放入一个Hashtable里。然后轮询长字符串,具体算法如下:
a、hash[26],先全部清零,然后扫描短的字符串,若有相应的置1,
b、计算hash[26]中1的个数,记为m
c、扫描长字符串的每个字符a;若原来hash[a] == 1 ,则修改hash[a]= 0,并将m减1;若hash[a] == 0,则不做处理
d、若m == 0 or 扫描结束,退出循环。
代码如下:
int hash[26] = {0};
// num为辅助数组中元素个数
int num = 0;
// 扫描短字符串
for (int j = 0; j < str2.length(); j++)
{
// 将字符转换成对应辅助数组中的索引
int index = str1[j] - 'A';
// 如果辅助数组中该索引对应元素为0,则置1,且num++; 这样也适用于str2 中有重复字母的情况。
if (hash[index] == 0)
{
hash[index] = 1;
num++;
}
}
// 扫描长字符串
for (int k = 0; k < str1.length(); k++)
{
int index = str1[k] - 'A';
// 如果辅助数组中该索引对应元素为1,则num--;为零的话,不作处理(不写语句)。
if(hash[index] == 1)
{
hash[index] = 0;
num--;
if(num == 0) //m==0,即退出循环。
break;
}
}
// num为0说明长字符串包含短字符串内所有字符
if (num == 0)
cout << "true" <<endl;
else
cout << "false"<< endl;
5: O(n+m)的素数方法
给每个字母分配一个素数,从2开始,往后类推。这样A将会是2,B将会是3,C将会是5,等等。具体思路如下:
a.定义最小的26个素数分别与字符'A'到'Z'对应。
b.遍历长字符串,求得每个字符对应素数的乘积。
c.遍历短字符串,判断乘积能否被短字符串中的字符对应的素数整除。
d.输出结果。
上述算法的时间复杂度为O(m+n),空间复杂度为O(1)。该算法提供的“是一种更、更、更有趣的方案。”,代码如下:
int primeNumber[26] = {2, 3, 5, 7, 11, 13, 17, 19,23, 29, 31, 37, 41, 43, 47, 53, 59,
61, 67, 71, 73, 79, 83,89, 97, 101};
int main()
{
string strOne = "ABCDEFGHLMNOPQRS";
string strTwo = "DCGSRQPOM";
// 这里需要用到大整数
CBigInt product = 1; //大整数除法的代码,下头给出。
// 遍历长字符串,得到每个字符对应素数的乘积
for (int i = 0; i < strOne.length(); i++)
{
int index = strOne[i] - 'A';
product = product * primeNumber[index];
}
// 遍历短字符串
for (int j = 0; j< strTwo.length(); j++)
{
int index = strTwo[j] - 'A';
// 如果余数不为0,说明不包括短字串中的字符,跳出循环
if (product % primeNumber[index] != 0)
break;
}
// 如果积能整除短字符串中所有字符则输出"true",否则输出"false"。
if (strTwo.length() == j)
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
6:Bit-map方法
所谓的Bit-map就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。由于采用了Bit为单位来存储数据,因此在存储空间方面,可以大大节省。
先举几个Bit-map的用例:
a:排序:
#define BYTESIZE 8
//将位数组p的第posi位置为1
void SetBit(char *p, int posi)
{
for(int i=0; i< (posi/BYTESIZE); i++)
{
p++;
}
*p =*p|(0x01<<(posi%BYTESIZE)); //将该Bit位赋值1
return;
}
void BitMapSortDemo()
{
//为了简单起见,我们不考虑负数
int num[] = {3,5,2,10,6,12,8,14,9};
//BufferLen这个值是根据待排序的数据中最大值确定的
//待排序中的最大值是14,因此只需要2个Bytes(16个Bit)
//就可以了。
const int BufferLen= 2;
char *pBuffer = new char[BufferLen];
//要将所有的Bit位置为0,否则结果不可预知。
memset(pBuffer, 0, BufferLen);
for(int i=0; i<9; i++)
{
//首先将相应Bit位上置为1
SetBit(pBuffer, num[i]);
}
//输出排序结果
for(i=0; i<BufferLen; i++) //每次处理一个字节(Byte)
{
for(int j=0; j<BYTESIZE; j++) //处理该字节中的每个Bit位
{
if((*pBuffer & (0x01<<j)) == (0x01<<j))
{
printf("%d ", i*BYTESIZE+ j);
}
}
pBuffer++;
}
printf("\n");
}
b:已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
8位最多99 999 999,大概需要99m个bit,大概10几m字节的内存即可。
c:2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
将 bit-map扩展一下,用2bit表示一个数即可,0表示未出现,1表示出现一次,2表示出现2次及以上,在遍历这些数的时候,如果对应位置的值是0, 则将其置为1;如果是1,将其置为2;如果是2,则保持不变。
利用bit-map解决字符串是否包含问题的思路是:每个字母的ASCII码值,可以对应一个位图中的位。先遍历第一个长字符串,生成一个“位图字典”。 然后,遍历第二个短字符串,用查字典的方式较检即可。算法时间复杂度为O(m+n),空间复杂度为O(1)。代码如下:
#define getbit(x) (1<<(x-'a'))
void a_has_b(char * a, char * b)
{
int i = 0;
int dictionary = 0;
int alen = strlen(a);
int blen = strlen(b);
for(i=0; i<alen; i++)
dictionary |= getbit(a[i]);
for(i=0; i<blen; i++)
{
if(dictionary != (dictionary|getbit(b[i])))
break;
}
if(i == blen)
printf("YES! A hasB!/n");
else
printf("NO! Char at %d is not found indictionary!/n",i);
}
该算法与HASHTABLE算法的本质相同,只是形式不一样而已。
7:字符串匹配问题
题目描述:
假设两个字符串中所含有的字符和个数都相同我们就叫这两个字符串匹配,比如:abcda和adabc,由于出现的字符个数都是相同,只是顺序不同,所以这两个字符串是匹配的。
要求高效实现下面的函数: boolenIs_Match(char *str1,char *str2)。
分析:可以看出,此字符串的匹配问题,是与上述字符串包含的问题相类似的,这个问题可以先排序再比较,也可以利用hash表进行判断。这里给出一种hash表的方法,原理已在上文中阐明了,代码如下:
bool Is_Match(const char *strOne, const char *strTwo)
{
int lenOfOne= strlen(strOne);
int lenOfTwo = strlen(strTwo);
// 如果长度不相等则返回false
if (lenOfOne != lenOfTwo)
return false;
// 开辟一个辅助数组并清零
int hash[26]= {0};
// 扫描字符串
for (int i = 0; i < strlen(strOne); i++)
{
// 将字符转换成对应辅助数组中的索引
int index = strOne[i] - 'A';
// 辅助数组中该索引对应元素加1,表示该字符的个数
hash[index]++;
}
// 扫描字符串
for (int j = 0; j < strlen(strTwo); j++)
{
int index = strTwo[j] - 'A';
// 如果辅助数组中该索引对应元素不为0则减1,否则返回false
if (hash[index]!= 0)
hash[index]--;
else
return false;
}
return true;
}
http://blog.youkuaiyun.com/v_JULY_v/article/details/6347454