一道题:给一个字符串,和一个字符集,求该字符串包含所有字符集的最短子串

比如字符集“abc”.个数为M。
字符串:“xxxbxxaxbxxbaxxaaxxcxx”
可知要求的最短子串是:baxxaaxxc。仔细分析最短串发现:
1。b与c在最短串中仅出现一次,正好是一头一尾。
2。中间的字符a,在最短串中可能出现了多次,但对于a而言如果只看它最后一次出现在最短串中,则之前不管是否出现过的都是无所谓的,比如最短串部分改成“bxxxxaxxc”,并不影响结果。

综合考虑上述两点,决定最短串长度的,只是字符集里的所有字符在最短串中最后一次出现的索引。为此,可设一个长为字符集个数M的数组,当遍历字符串时,不停的更新各字符
集最后出现的索引。同时,每字符集里的字符出现时,就要根据现有记录的索引去判断是否有最短子串产生。

时间复杂度:每次遍历遇到字符集出现,做一个判断O(M),因此总复杂度为O(N*M)。



#include <stdio.h>
#include <stdlib.h>

#define N 100
#define M 3

char str[N] = "xbxaxaxbxaxcxcaxbxxaxbxcxxaxbcxaxcbcc";//母串
char ch[M] = "abc";//字符集

/*
函数功能:测试字符c是否在字符集中,若在,返回该字符在字符集中的索引
*/
int is_exit(char c)
{
	int i=0;
	for(i=0;i<M;++i)
	{
		if( c == ch[i] )
			return i;
	}
	return -1;
}
/*
函数功能:根据目前记录的各字符集索引,算出最短子串长,子串的起始索引
*/
void dis(int* mindis_idx,int *pdis,int* start_idx)
{
	int min_idx = 0;
	int min_val = mindis_idx[0];
	int max_idx = 0;
	int max_val = mindis_idx[0];
	int i=1;
	for(;i<M;++i)
	{
		if( mindis_idx[i] == -1  )
			return;

		if( mindis_idx[i] < min_val  )
		{
			min_val = mindis_idx[i];
			min_idx = i;
		}

		if( mindis_idx[i] > max_val  )
		{
			max_val = mindis_idx[i];
			max_idx = i;
		}
	}
	*pdis = max_val - min_val +1;
	*start_idx = min_val;
}

void show_substr(int start_idx,int d)
{
	char * ss = (char*)malloc(d+1);
	ss[d]=0;
	if(ss)
	{
		memcpy(ss,str+start_idx,d);
		printf("The substring=%s\n",ss);
	}
	free(ss);
}

void main()
{
	int mindis_idx[M];//记录当前字符集里每个字符在母串中的“当前”最大索引,这里的“当前”是指遍历索引的当前值
	memset(mindis_idx,-1,M*sizeof(int)); //初始化为-1,这非常关键,当计算最短距时,若遇到有-1,证明目前还没收集满所有字符集

	int d = N;//最短子串长
	int start_idx = 0;//当前最短子串的起始索引
	int i=0;
	for(;i<N;++i)
	{
		int r=0;
		if( (r=is_exit(str[i]))!=-1 )//找到一个在字符集中的元素
			mindis_idx[r] = i;
		
		int temp_d = d;
		int temp_start_idx = start_idx;
		dis(mindis_idx,&temp_d,&temp_start_idx);//测试当前索引i的最短子串
		if( temp_d < d )
		{
			d = temp_d;
			start_idx = temp_start_idx;
		}
	}

	printf("The min dis=%d\n",d);
	show_substr(start_idx,d);
}






  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值