字符串的问题 (kmp的next数组)(2021-08-15)

字符串的问题

题目
有一个字符串 让你找到这个字符串 S 里面的子串T 这个子串 T 必须满足即使这个串的前缀 也是这个串的后缀 并且 在字符串中也出现过一次的(提示:要求满足前后缀的同时也要在字符串中出现一次 只是前后缀可不行 输出最长满足要求字符串)
输入:
给出一个字符串 长度 1 到 1e6 全部是小写字母
输出:
如果找的到就输出这个子串T 如果不行就输出 Just a legend
示例1
输入
fixprefixsuffix
输出
fix
示例2
输入
abcdabc
输出
Just a legend

转载于:代码来源here
思路:
1.KMP算法的next数组储存的是next[j]:
(1)前j-1个数的最长相同前后缀长度
(2)字符不匹配时,应该回溯到的字符的下标
2.next[s.length()]中存的是字符串s的最长相同前后缀长度
3.重点:我们要如何找中间有没有和相同前后缀一样的字符串?
(1)给中间的字符串i∈[1,s.length())的next数组的值进行标记,vis[next[i]]=1,如果,vis[next[s.length()]]==1,则说明中间还出现过一次长度为next[s.length()]的字符串 (vis数组标记的是中间有没有出现过(存的是next数组[1,len)的值)
eg:s字符串
abcabcab
next下标 前缀 next值(最长相同前后缀)往后挪了一个
0 ---------- a ---------- -1
1 ---------- ab ---------- 0
2 ---------- abc ---------- 0
3 ---------- abca ---------- 0
4 ---------- abcab ---------- 1
5 ---------- abcabc ---------- 2
6 ---------- abcabca ---------- 3
7 ---------- abcabcab ---------- 1
8----------- s.length() ------------2
因为next[s.length.()]=2,vis[2]=1,说明中间有相同的后缀可以和前缀匹配,所以满足要求
(2)否则,让 len=next[len] (len=s.length()) ,去查看开头长度为next[len]的字串有没有循环节,因为前后缀相同,若果前缀有的话,后缀也有,如果在中间查找时找到了字符串的第一个字符即前缀的位置,则说明没有。
eg: aaa
next下标 前缀 next值 vis
0 ---------- a ---------- -1--------0
1 ---------- aa ---------- 0--------1
2 ---------- aaa ---------- 1--------1
3 ---------- s.length()---------- 2--------0

vis[2]==0, 2!=0,进入一轮循环,x=next[2]=1 循环节为1
vis[1]==1 成立 输出长度为1的前缀

eg:ab
next下标 前缀 next值 vis
0 ---------- a ---------- 1--------0
1 ---------- ab ---------- 0--------1
2 ---------- s.length()---------- 0--------0

vis[0]=1,x=0,不进入循环,输出Just a legend

#include<iostream>
#include<string.h>
using namespace std;
const int maxn=1e6+5;
int Next[maxn];
int vis[maxn];
void prefix(string s){
	int j,k;
	j=0,k=-1;
	Next[0]=-1;
	int lens=s.length();
	while(j<lens){//Next [0,s.length()],Next[s.length()]存的是这个字符串的最长公共前后缀 
		if(k==-1||s[k]==s[j]){
			j++;
			k++;
			Next[j]=k;
		}
		else{
			k=Next[k];
		}
	}
// 	for(int i=0;i<=lens;i++){
// 		cout<<"next:"<<Next[i]<<endl;
// 	}
}

int main(){
	string s;
	cin>>s;
	memset(vis,0,sizeof(vis));
	int len=s.length();
	prefix(s);
	for(int i=1;i<len;i++){
		vis[Next[i]]=1;//vis标记中间有没有出现过
	}
	int x=Next[len];//如果vis[len]=1,说明中间出现过,此时就是答案
// 	for(int i=0;i<=len;i++){
// 		cout<<"vis"<<i<<":"<<vis[i]<<endl;
// 	} 
	while(!vis[x]&&x!=0){//vis标记是否中间出现过,若出现过直接跳出,否则继续找循环节,若x=0,说明找到了字符串的第一个字符,说明此时没有匹配的答案
		x=Next[x];
	}
	if(x==0){
		cout<<"Just a legend"<<endl;
	}
	else{
		for(int i=0;i<x;i++){
			cout<<s[i];
		}
		cout<<endl;
	}
	return 0;
}

(其实,我也还有点懵!!!)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值