问题描述
给定两个字符串a和b,一长一短,如何快速判断短字符串b中所有元素都在长字符串a中?这里假设字符串中的元素都为大写字母
【例 如】
- a = “ABCD”,b = “DBA”,返回true
- a = “ABCD”,b = “AAD”,返回true
- a = “ABCD”,b = “BCE”,返回false
算法的本质在于用更小的时间消耗获得同样的效果,所以,除了暴力轮询这种方法之外,我们应该努力寻找其他更快速有效的算法来解决这类问题。
方法一:排序+轮询查找
排序往往会使查找变得更加快捷,而众多排序算法中,快速排序在处理大数据集是很有优势的。当然对于本题,数据集可大可小,因此,如果要在排序算法上下功夫的话,可以采用上一文讲的C语言快速排序算法及三种优化方式 中3.4节的思想。
这里小编偷个小懒,采用一般快速排序(即不考虑 三数取中 和 小数据的情况,采用挖坑填坑的快排,具体参看上一文讲的C语言快速排序算法及三种优化方式)
step 1: 将a和b使用快排成为递增序列,复杂度o(nlogn)
step 2: b中元素与a匹配,匹配成功则返回false,复杂度O(m+n)
注意:不做排序的直接匹配,由于序列不是有序,因此每一次匹配都要把a数组遍历一次,复杂度O(n*m)
排序+查找代码实现
int first_ele_fast_sort( char array[] , int low , int high )//注意是针对字符串的快排 char
{
char target = array[low];//偷个懒,直接用数组中的第一个元素作为中轴元素
while( low < high )
{
while( low < high && array[high] >= target )
high--;
array[low] = array[high];//此时high位置上的元素为待处理元素
while( low < high && array[low] <= target )
low++;
array[high] = array[low];//将大于target值的元素放到待处理的high位置上,那么此时,low位置变为待处理
}
array[low] = target; //以上,省去交换,变为“挖坑赋值”,降低时间复杂度
return low;
}
void fast_sort( char array[] , int low , int high )
{
if( low < high )
{
int index;
index = first_ele_fast_sort(array,low,high);
fast_sort(array,low,index-1);
fast_sort(array,index+1,high);
}
}
bool string_contain( char a[] , int a_len , char b[] , int b_len )//a b均为字符串
{
fast_sort(a,0,a_len-1);//字符串a快排
fast_sort(b,0,b_len-1);//字符串b快排
int a_index,b_index;
for( b_index = 0 , a_index = 0 ; b_index < b_len ; b_index++ )//b在a中找伙伴,所以b为外循环
{
while( a_index < a_len && a[a_index] < b[b_index] )//a,b均为增序
a_index++;
if( a_index >= a_len || a[a_index] > b[b_index] )
return false;//b中任意一个元素轮询a后没找到匹配,则直接return false 无需再找下去了--降低复杂度
}
return true;
}
方法二:构建质数数组,取余判断
step 1:构建大小为26的质数数组,将26个字母分别用{1,2,3,5,7,11…}这些质数元素代表;
step 2:将a数组里对应的所有质数相乘,得到一个积;
step 3:用积对b数组中每个元素对应的质数取余,如果余数为0,说明此元素在a数组中,反之则不在
本算法有个缺陷,因为质数相乘的结果会很大,从而导致整数溢出(前16个字母对应的质数相乘会超出long long类型所能刚表示的最大整数边界),所以这种方法不可取。
bool StringContain(char* str1, char* str2)
{
int mapping[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};
//遍历一遍str1表,转成素数乘积
int i;
int x=0;
long long res1 = 1;
for(i=0; i<strlen(str1); i++)
res1 = res1*mapping[str1[i]-'A'];
//遍历一遍str2表,用res1与每一个元素做除法,一旦有余数则说明不包含
for(i=0; i<strlen(str2); i++)
{
x = mapping[str2[i]-'A'];
if(res1%x!=0)
return false;
}
return true;
}
方法三:构建哈希表
step 1: 将a中元素放入哈希表
step 2: b中元素通过哈希地址访问哈希表,若找到相同元素,则false
本方法的时间复杂度为:O(1)
构建哈希表方法代码实现
1、传统方法
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAXSIZE 100
typedef struct hash_node//链表法避免散列表冲突
{
int element;
struct hash_node *next;
}hash_node,hash_link[MAXSIZE];
hash_link L;//全局变量,方便后几个函数承接
int hash_fun( int at_frist )
{
return at_frist%MAXSIZE;
}//取余法构建哈希函数
void char2hashList( char array[] , int array_len )
{
int i;
//哈希表初始化
for( i = 0 ; i < MAXSIZE ; i++ )
{
L[i].element = i;
L[i].next = NULL;
}
//字符串插进哈希表中
for( i = 0 ; i < array_len ; i++ )
{
hash_node *p;
p = (hash_node*)malloc(sizeof(hash_node));
p->element = array[i] - 'A';
int hash_index = hash_fun(array[i]-'A');
//头插法插入hash_index所在链表
p->next = L[hash_index].next;
L[hash_index].next = p;
}//以上MAXSIZE只要足够大于array_len,基本保证每个hash_index的链表就一个元素,大大降低了后面的查找时间
}//函数完成将a数组元素放入哈希表中
bool string_contain( char a[] , int len_a , char b[] , int len_b )
{
int i;
//先将a纳入哈希表
char2hashList(a,len_a);
for( i = 0 ; i < len_b ; i++ )
{
int b_hash_index = hash_fun(b[i]-'A');//将数组b的元素进行哈希地址转换
hash_node *p;
p = L[b_hash_index].next;//p指向b经哈希地址转换后的结点
while(p)
{
if(p->element != b[i]-'A')//在b_hash_index下标所在的链表中,找与b[i]-'A'匹配的元素
p = p->next;
else
break;
}
if( !p )
return false;
}
return true;
}
简单哈希
根据题目特点,构建仅含26个字母的特殊哈希表,哈希函数映射规则为:index = str[i]-‘A’ hash[index]=true/false
bool StringContain(char* str1, char* str2)
{
/**将str1的元素按照哈希函数的规则存入哈希表**/
bool hash[26]={false};//0-false表示没有,1-true表示有
int i=0;
for(i=0; i<strlen(str1); i++)
{
int index = str1[i]-'A';
if(hash[index] == false)
hash[index] = true;
}//这样一来,array数组就记录了仅由字母组成的字符串str1中,所包含的字母情况
//接下来遍历str2字符串,一旦通过相同的哈希规则没在哈希表array中找到所映射的字母,则直接返回false
for(i=0; i<strlen(str2); i++)
{
int index = str2[i]-'A';
if(hash[index]==false)
return false;
}
return true;
}