题目:请判断一个字符串里的字符是否全部都包含在另一字符串中。
例:s1:ABCDEFGHIJKLMN(要求s2包含于s1);若s2:AEICDFGFBH,返回true;若s2:CDQGEJMLP,返回false。
最容易想到的是轮询,即s2中的每一个字符与s1中的所有字符依次比较,但是这种方法的时间复杂度为O(n*m),最坏的情况每个字符都要比较n次(因为字符可以重复)。另一种方法是先排序再进行比较,对两个字符串的排序需要O(m log m)+O(n log n)次操作,之后的线性扫描需要O(m+n)次操作,这个方法的优势随着字符串的增长而变大。
下面这实现中用到快速排序,具体如下:
更好的方法是使用hashtable,思路是:1、hash[26],清零,扫描短字符串,在相应位置置1,并累加总数m;2、扫描长字符串,若相应位置为1,置0,同时m减1;若为0则不处理;3、若m == 0或扫描结束,退出循环。时间复杂度是O(m+m)至O(n+m)。
最后还有一个方法:利用素数(质数)互质原理,定义最小的26个素数分别与26个字母对应,遍历长字符串,求得每个字符对应素数的乘积,遍历短字符串,判断乘积能否被短字符串中字符对应素数整除,循环判断。时间复杂度O(n)至O(n+m),与上面的方法的优劣要根据具体字符串而定。
具体算法如下:
上面哈希表的方法也可以通过位图来实现(先扫描短字符串的情况下其实是否要加上计数值这个东西我认为应该是要看具体情况而定,假如字符串比较短的话可以考虑不加;另外用位图的话空间上的压缩使得可以采用先扫描长字符串再进行位置查找判断,没有多余操作,以下方法基于后者,不见得最优,但比较直观):
例:s1:ABCDEFGHIJKLMN(要求s2包含于s1);若s2:AEICDFGFBH,返回true;若s2:CDQGEJMLP,返回false。
最容易想到的是轮询,即s2中的每一个字符与s1中的所有字符依次比较,但是这种方法的时间复杂度为O(n*m),最坏的情况每个字符都要比较n次(因为字符可以重复)。另一种方法是先排序再进行比较,对两个字符串的排序需要O(m log m)+O(n log n)次操作,之后的线性扫描需要O(m+n)次操作,这个方法的优势随着字符串的增长而变大。
下面这实现中用到快速排序,具体如下:
#include <iostream>
#include <string>
using namespace std;
int partition(string &str,int lo,int hi)
{
int key = str[hi]; //以最后一个元素,str[hi]为主元
int i = lo - 1;
for(int j = lo; j < hi; j++) //不断将元素与主元作比较,比主元小的元素依次与字符串前端的元素互换
{
if(str[j] <= key)
{
i++;
swap(str[i], str[j]);
}
}
swap(str[i+1], str[hi]); //最后将主元插入到较小元素和较大元素的边界
return i + 1; //返回主元位置
}
//递归调用上述 partition 过程,完成排序。
void quicksort(string &str, int lo, int hi)
{
if (lo < hi)
{
int k = partition(str, lo, hi);
quicksort(str, lo, k - 1);
quicksort(str, k + 1, hi);
}
}
void compare(string str1,string str2)
{
int posOne = 0;
int posTwo = 0;
while (posTwo < str2.length() && posOne < str1.length())
{
while (str1[posOne] < str2[posTwo] && posOne < str1.length() - 1) //str1移动到与str2首字符相等处
posOne++;
if (str1[posOne] != str2[posTwo])
break; //不相等则结束输出false
posTwo++;
}
if (posTwo == str2.length())
cout << "true" << endl;
else
cout << "false" << endl;
}
int main()
{
string str1 = "ABCDEFGHLMNOPQRS";
string str2 = "DCGDSRQPOM";
quicksort(str1, 0, str1.length() - 1);
quicksort(str2, 0, str2.length() - 1); //先排序
compare(str1, str2); //后线性扫描
return 0;
}
接着上面的,把快速排序换成计数排序,计数排序的优点是在一定范围的整数排序时,快于任何的比较排序算法,它的时间复杂度是O(n+k)(k是整数的范围)。这样一来总计时间复杂度就变成了3*O(n+m)=O(n+m)了。这种做法的一个缺点是要消耗相应的空间做辅助。
具体实现代码除了计算排序部分与上面的类似,只贴出计数排序部分:// 计数排序,O(n+m)
void CounterSort(string str, string &help_str) //help_str用于存储排序后的字符串
{
// 辅助计数数组
int help[26] = {0};
// help[index]存放了等于 index + 'A'的元素个数
for (int i = 0; i < str.length(); i++)
{
int index = str[i] - 'A';
help[index]++;
}
//累计求出每个元素前有多少个比自己小的元素,进而确定位置
for (int j = 1; j < 26; j++)
help[j] += help[j-1];
// 把每个元素放到help_str中对应的最终位置
for (int k = str.length() - 1; k >= 0; k--)
{
int index = str[k] - 'A';
int pos = help[index] - 1;
help_str[pos] = str[k];
help[index]--;
}
}
更好的方法是使用hashtable,思路是:1、hash[26],清零,扫描短字符串,在相应位置置1,并累加总数m;2、扫描长字符串,若相应位置为1,置0,同时m减1;若为0则不处理;3、若m == 0或扫描结束,退出循环。时间复杂度是O(m+m)至O(n+m)。
具体算法如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1="ABCDEFGHLMNOPQRS";
string str2="DCGSRQPOM";
// 开辟一个辅助数组并清零
int hash[26] = {0};
// num 为辅助数组中元素个数
int num = 0;
// 扫描短字符串
for (int j = 0; j < str2.length(); j++)
{
// 将字符转换成对应辅助数组中的索引
int index = str1[j] - 'A';
// 如果辅助数组中该索引对应元素为 0,则置 1,且 num++;
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;
return 0;
}
最后还有一个方法:利用素数(质数)互质原理,定义最小的26个素数分别与26个字母对应,遍历长字符串,求得每个字符对应素数的乘积,遍历短字符串,判断乘积能否被短字符串中字符对应素数整除,循环判断。时间复杂度O(n)至O(n+m),与上面的方法的优劣要根据具体字符串而定。
具体算法如下:
#include <iostream>
#include <string>
#include "BigInt.h"
using namespace std;
// 素数数组
int primeNumber[26] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 4
7, 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;
}
这个方法似乎要用到大整数除法,而且也有不足的地方,贴出来只为了了解下思路。
上面哈希表的方法也可以通过位图来实现(先扫描短字符串的情况下其实是否要加上计数值这个东西我认为应该是要看具体情况而定,假如字符串比较短的话可以考虑不加;另外用位图的话空间上的压缩使得可以采用先扫描长字符串再进行位置查找判断,没有多余操作,以下方法基于后者,不见得最优,但比较直观):
#include <stdio.h>
#include <string.h>
#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 has B!/n");
else
printf("NiO! Char at %d is not found in dictionary!/n",i);
}
int main()
{
char * str1="abcdefghijklmnopqrstuvwxyz";
char * str2="akjsdfasdfiasdflasdfjklffhasdfasdfjklasdfjkasdf";
char * str3="asdffaxcfsf";
char * str4="asdfai";
a_has_b(str1, str2);
a_has_b(str1, str3);
a_has_b(str3, str4);
return 0;
}
大致上到这里了,以上代码参考自《程序员编程艺术》,只不过注释改成了我能理解的形式而已。