编程艺术之第二章:字符串包含

来源:互联网 发布:matlab智能算法工具箱 编辑:程序博客网 时间:2024/05/01 19:40

题目描述:有一个较长的字符串A和较短的字符串B,如何快速查找是否A全部包含B,即B字符串里的字符A中都有。注意:不用顺序一致,只要包含即可。

如:StringA:ABCDEFGHLMNOPQRS

        StringB:DCGSRQPO

则返回true。


思路1:

看到这题,我的第一反应就是逐个比较(毕竟我的经验不多,想法比较单纯~~~)。获取A和B两个字符串的长度,假设B较短,则把B中的字符一个个提取出来与A中的进行比较。如果length(A) = m,length(B) = n,且m>n,则最坏情况下时间复杂度是O(n*m)。这样的方法效率比较低,想想有木有改善的地方?

思路1的代码如下:

#include <iostream>#include <stdio.h>using namespace std;bool compare_chars(char* str1,char* str2);int get_char_length(char* str);int main(){char A[] = "ABCDEFGHLMNOPQRS";char B[] = "DCGSRQPOZ";printf("A的长度是%d\nB的长度是%d\n",get_char_length(A),get_char_length(B));bool isCompare = compare_chars(A,B);if(isCompare)printf("包含");elseprintf("不包含");getchar();return 0;}int get_char_length(char* str){if(str == NULL)return -1;else{int length = 0;while (str[length++] != '\0');return length-1;}}//思路1bool compare_chars(char* str1,char* str2){char temp;const int length_A = get_char_length(str1);const int length_B = get_char_length(str2);for (int i = 0 ;i < length_B;i++){temp = *(str2+i);int j = 0;while(temp != *(str1 + j) && j < length_A)j++;if(length_A == j)return false;}return true;}


思路2:

可以先把两个字符串进行排序,好的排序算法可以降低时间复杂度,而排序完成之后就可以对两个字符串进行轮询扫描。假设两个字符串是从小到大进行排序的,再进行扫描时,先建立两个指针分别指向两个字符串的起点(pA,pB)

如果*(pA) < *(pB) 则pB不动,pA++向后移动

如果*(pB) < *(pA) 则pA不动,pB++向后移动

如果B字符串中有一个字符是未在A中发现与其相等的字符(即*(pA) < *(pB)时,pA++,然后*(pA) > *(pB),此时表示pB未等于过,所以pB查找失败),B字符串后续的字符也不用进行查询了。

最坏情况下是O(m+n)的时间复杂度。

加上排序所消耗的时间复杂度,总体时间复杂度 应该是O(mlongm)+O(nlogn)+O(m+n)

思路2的代码如下:

//思路2void quick_sort(char*str,int left,int right){char f,t;int rtemp,ltemp;rtemp = right;ltemp = left;f = *(str + (left + right)/2);while(ltemp < rtemp){while(*(str + ltemp) < f)++ltemp;while(*(str + rtemp) > f)--rtemp;if (ltemp < rtemp){t = *(str+ltemp);*(str+ltemp) = *(str+rtemp);*(str+rtemp) = t;++ltemp;--rtemp;}}if (ltemp == rtemp){ltemp++;}if (left < rtemp){quick_sort(str,left,ltemp-1);}if (right > ltemp){quick_sort(str,rtemp+1,right);}}bool compare_chars(char* str1,char* str2){//1 先进行排序---快速排序const int length_A = get_char_length(str1);const int length_B = get_char_length(str2);quick_sort(str1,0,length_A-1);quick_sort(str2,0,length_B-1);//2 轮询比较int i = 0, j = 0;while(i<length_A && j < length_B){if(*(str1+i) < *(str2+j))i++;else if(*(str1+i) == *(str2+j)){if (i == length_A - 1 && j == length_B - 1)return true;else{j++;i++;}}elsereturn false;}if (str1[i] == '\0')return false;return true;}


思路3:

既然思路2的时间复杂度是O(mlogm)+O(nlogn)+O(m+n),那么有没有可能将时间复杂度极限成O(m+n)?是不是可以使用第三方存储空间?有些算法不允许使用。所以我就在想,既然是大写英文字符,那么最多就是26个,如果先把B字符串里的字符全部记录下来,再让A来比较就好了,只不过这需要中间的一个桥梁,毕竟进行B标记的与A比较的应该相同。所以可以构建一个数组,长度为26(26是个关键长度)。

初始化:int position[26] = {0};

针对B字符串的对应的26个英文字母的序号填充position数组,若出现,则将值置为1,否则保持0,最后记录长度为m。

序列完B之后,将A的字符也进行比较,如果也出现同样的字符,则将1置为0,同时m-1,如果未出现,则保持不变。

最后检测m的长度是否为0,如果不为0,说明false,反正为true.

这就实现了对每个字符串里的字符只访问一次。

思路3的代码如下:

后来发现这就是hashtable的想法啊,= =!!!-。-

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;  } 


思路4

思路4之所以用蓝色标记是有因为这并不是我想出来的,但是它实在很巧妙,重要的是该方法的确是上一个方法的提升,弥补了上一个办法的缺点。
我们先讨论下上一个方法,如果采用上一个方法,我们必须进行扫描完全才可以,就是说进行A字符串扫描的时候必须全部扫描完,除非事先先将A数据进行排序,那么就可以在第一个position[index] = 1不被置为0的情况下就将结果置为false。不然的话你会发现不排序的A数组后面可能还有机会能将这个地方的1置为0。但是排序会提高时间复杂度。所以有没有更好的方法能够提前作出预判?
这篇文章的方法就很好的解决了这个问题,它的思路如下:
1、首先定义最小的26个素数分别与字符'A'到‘Z’对应。
2、遍历A字符串,求得每个字符对应的素数值,获得乘积result。
3、将此乘积去除以B字符对应的素数,如果有余数,则表示B数组的这个素数没在A里出现过,意思就是B的字符没出现在A中,则返回false
4、输出结果
相信各位已经明白了,巧妙的采用素数的方法来暗指字符,采用乘积来描述一个序列,采用余数来进行判断。以上两种方法都有一个共同点,就是寻求适合两者的一个共同标准,当A满足了标准而B却向矛盾时,则over。这是算法的入口。

思路4 的代码如下:

#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, 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;  }  


0 0
原创粉丝点击