【百度面试题】求包含固定字符集的最短子串

本文提供了一种算法,用于在给定的珠串中找到包含所有颜色且长度最短的子串,同时分析了其时间复杂度和空间复杂度。通过一次遍历即可解决此问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:

一串首尾相连的珠子(m个),有N种颜色(N<=10),设计一个算法,取出其中一段,要求包含所有N中颜色,并使长度最短。并分析时间复杂度与空间复杂度。

问题可等同于求一个长字符串c中包含固定字符集target的最短子串

如"abddcbda"中包含"abc"的最短子串是"cbda"

算法:

指针head,rear分别指向目前已知的最短子串,初始值为c的头和尾

指针begain,end是当前检查的子串,一旦该串包含了全部target,就比较它和已知最短子串的长度,若它更短,设为新的最短子串

数组targetlist记录target中每个元素在begain-end段中出现的次数

begain end初始值均为c的头,对begain end进行的操作如下:

若end在target中,targetlist中相应元素(end出现的次数)加一,转②若end不在target中转④

从begain开始检查,若begain出现的次数大于1或者begain不在target中,begain前移,继续检查,直到begain只出现一次。这一步保证begain-end段是在不丢失target的情况下最短的转③

检车begain-end段,一旦该段包含了全部target,就比较它和已知最短子串(head-rear段)的长度,若它更短,设为新的最短子串。begain++,转④

end++转①


这样只需一次遍历,就可以找出最短子串,时间复杂度为O(n),n为c长度

空间复杂度为O(n+m) ,m为字符集target中元素的个数


代码实现:

#pragma once
#include<iostream>
using namespace std;

bool Intarget(char* end, char* target, int* clist, int* (&targetlist), int count)
{
	for (int i = 0; i < (int)strlen(target); i++)
	{
		if (*(target + i) == *end)
		{
			targetlist[i]++;//出现此事加1
			clist[count] = i;//end指向的元素是target中第i个元素
			return true;
		}
	}
	return false;
}

//缩减begain-end段,保证它里面目标个数不变的情况下,该段最小
void Reduce(char*(&c), char*(&target),char*(&begain), char*(&end), int*(&clist), int* (&targetlist))
{
	for (int i = begain - c; i <= end - c; i++)
	{
		if (targetlist[clist[i]] > 1)//begain指向的元素出现次数大于1,,begain前移
		{
			begain++;
			targetlist[clist[i]]--;
		}
		else if (clist[i] == -1)//begain指向的元素不在target中,begain前移
			begain++;
		else
			return;
	}
}

//检查begain-end是否包含所有target,若是,且它长度小于head-rear段,将它设为新的head-rear
void Checkall(char*(&c), char*(&target), int*targetlist, int* clist, char*(&begain), char*(&end), char*(&head), char*(&rear))
{
	bool all = true;
	//检查begain-end这一段,target中的元素是否全都出现
	for (int i = 0; i < (int)strlen(target); i++)
		if (targetlist[i]>0)
			continue;
		else
		{
			all = false;
			break;
		}
	//head-rear取小值
	if (all&& end - begain < rear - head)
	{
		head = begain;
		rear = end;
		targetlist[clist[begain - c]]--;
		begain++;
	}
}

void Find(char* c, char* target, char* (&head), char* (&rear))
{
	int *targetlist = new int[strlen(target)];
	for (int i = 0; i < (int)strlen(target); i++)//target中元素已出现的次数
		targetlist[i] = 0;
	int *clist = new int[strlen(c)];
	for (int i = 0; i < (int)strlen(c); i++)//c中第i个元素在target中的位置
		clist[i] = -1;

	char *begain, *end;//当前正在检查的段
	head = c;//指向头
	rear = head + strlen(c)-1;//指向尾
	begain = end = c;
	while (*end)
	{
		if (Intarget(end, target, clist, targetlist, end - c))//如果*end在target中
		{
			//缩减begain-end段
			Reduce(c, target, begain, end, clist, targetlist);
			//检查begain-end段是否包含所有target,是,则比较长度,小了就生成新的head,rear
			Checkall(c, target, targetlist,clist, begain, end, head, rear);
		}
		end++;
	}
}

void  main()
{
	char* c = "abddcbdabcd";
	char* target = "abc";
	int *targetlist = new int[strlen(target)];
	char *head = new char;
	char* rear = new char;//保存最小段
	Find(c, target, head, rear);
	cout << "最短子串长度:" << rear - head +1<< endl;
	cout << "最短子串:";
	for (int i = 0; i <= rear - head; i++)
		cout << *(head + i)<<' ';
	cout << endl;
	cout << "最短子串的head位置:" << head - c << endl;
	cout << "最短子串的rear位置:" << rear - c << endl;
	system("pause");
}

运行结果:



### 关于字符串循环子串问题 对于字符串循环子串问题,可以采用多种方法来解决。一种高效的方法是基于 KMP 算法的前缀函数计算得出的结果来进行处理[^2]。 #### 方法概述 通过构建一个新的字符串 `s+s`(即原始字符串与其自身的连接),并去除第一个和后一个字符,可以在新形成的字符串中查找是否存在与原字符串相等的子串。如果存在,则说明该字符串具有周期性;否则不具周期性。具体实现如下: 1. 构建辅助字符串 `concatenatedStr = s + s` 并去掉首尾字符。 2. 使用KMP算法中的部分匹配表(PMT)长公共前后缀长度数组 next[]。 3. 判断条件:设原字符串长度为len(s),若next[len(s)-1]!=-1且 len(s)%(len(s)-(next[len(s)]+1))==0 成立,则表明有循环节。 这种方法的时间复杂度主要取决于构造PMT的过程以及后续的一次遍历操作,整体上能够保持在线性时间内完成。 ```python def shortest_cyclic_substring(s): concatenated_str = (s * 2)[1:-1] def compute_pmt(pattern): pmt = [-1] * len(pattern) i, j = 0, -1 while i < len(pattern) - 1: if j == -1 or pattern[i] == pattern[j]: i += 1 j += 1 pmt[i] = j else: j = pmt[j] return pmt pmt_result = compute_pmt(concatenated_str) length_s = len(s) if pmt_result[length_s - 1] != -1 and \ length_s % (length_s - (pmt_result[length_s - 1]+1)) == 0: cyclic_length = length_s // ((length_s - (pmt_result[length_s - 1]+1))) return s[:cyclic_length], True return s, False print(shortest_cyclic_substring("abcabc")) # 输出 ("abc", True), 表明 "abc" 是循环子串 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值