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

题目描述:有一个较长的字符串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("包含");
	else
		printf("不包含");
	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;
	}
}
//思路1
bool 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的代码如下:

//思路2
void 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++;
			}
		}
		else
			return 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值