试题 算法训练 后缀数组——最长重复子串

本文探讨了如何使用后缀数组和最长公共前缀(LCP)来解决给定字符串中至少出现k次的最长重复子串问题,涉及了倍增算法、基数排序以及二分查找策略。通过实例和代码展示了如何计算重叠子串的长度,适用于长度为n的数串场景。
摘要由CSDN通过智能技术生成

资源限制
时间限制:100ms 内存限制:256.0MB


问题描述
  给定一个长度为n的数串,求至少出现k 次的最长重复子串的长度,这k 个子串可以重叠。保证有子串出现至少k次。


输入格式
  第一行:两个整数n, k;
  第二行:2到n + 1行:n个整数,这n个整数组成了一个数串。
输出格式
  一个整数,表示最长重复子串的长度。


样例输入
8 2
1 2 3 2 3 2 3 1
样例输出
4


数据规模和约定
  0 ≤ n ≤ 20000, 2 ≤ k ≤ n,0≤ 数串中的整数≤ 1000000


后缀数组:

  • suff[i]:表示以第i位为开头的后缀。
  • 后缀数组sa[i]:表示所有后缀在排完序后,排名为i的后缀在原串中的位置。
  • 名次数组rank[i]:表示所有后缀在排序完后,原字符串中第i名现在的排名。
    三者关系:
    在这里插入图片描述

求后缀数组:

构造sa数组的方法一般有两种:

  • 倍增算法:O(nlogn)
  • DC3算法:O(n)

算法中会运用基数排序
倍增算法基数排序可自行去网上查阅了解。

LCP-最长公共前缀:

height[i]:表示suff[sa[i]]和suff[sa[i−1]]的最大公共前缀,也就是排名完后两个相邻的后缀的最长公共前缀。
图解:
在这里插入图片描述

后缀数组的应用:

1、可重叠最长重复子串

给定一个字符串,求最长重复子串,这两个子串可以重叠

2、不可重叠最长重复子串

给定一个字符串,求最长重复子串,这两个子串不可以重叠

3、可重叠的K次最长重复子串

给定一个字符串,求至少出现K次的最长重复子串,这K个子串可以重叠

能力有限,可自行了解:后缀数组——处理字符串的有力工具

本题源程序:

#include<iostream>
using namespace std;
#define maxsize 100000
int Rank[maxsize], sa[maxsize];
int height[maxsize];

int sec[maxsize], t[maxsize];
int s[maxsize];//接收用户输入的数组
int num = maxsize;

int len, number;//长度和次数
inline void SA() //获得sa[]数组
{
	for (int i = 1; i <= num; i++) t[i] = 0;
	for (int i = 1; i <= len; i++) ++t[Rank[i] = s[i]];
	for (int i = 1; i <= num; i++) t[i] += t[i - 1];
	for (int i = len; i >= 1; i--) sa[t[Rank[i]]--] = i;
	for (int k = 1; k <= len; k++) 
	{
		int cnt = 0;
		for (int i = len - k + 1; i <= len; i++) sec[++cnt] = i;
		for (int i = 1; i <= len; i++) if (sa[i] > k) sec[++cnt] = sa[i] - k;
		for (int i = 1; i <= num; i++) t[i] = 0;
		for (int i = 1; i <= len; i++) ++t[Rank[i]];
		for (int i = 1; i <= num; i++) t[i] += t[i - 1];
		for (int i = len; i >= 1; i--) sa[t[Rank[sec[i]]]--] = sec[i], sec[i] = 0;
		swap(Rank, sec);
		Rank[sa[1]] = 1, cnt = 1;
		for (int i = 2; i <= len; i++)
			Rank[sa[i]] = (sec[sa[i]] == sec[sa[i - 1]] && sec[sa[i] + k] == sec[sa[i - 1] + k]) ? cnt : ++cnt;
		if (cnt == len) break;
		num = cnt;
	}
}
void Getheight() //获得height[]数组
{
	int j, k = 0;   
	for (int i = 1; i <= len; i++) 
	{
		if (k) k--;  
		int j = sa[Rank[i] - 1];   
		while (s[i + k] == s[j + k]) k++;
		height[Rank[i]] = k;
	}
}
bool check(int mid)//利用height[]数组进行分类,然后统计组内是否满足重复的次数。
{
	int group = 0;
	for (int i = 1; i <= len; i++)
	{
		if (height[i] >= mid)
			group++;
		if (group >= number)
			return true;
		if (height[i] < mid)
			group = 1;
	}
	return false;
}
int main() 
{
	cin >> len >> number;
	for (int i = 1; i <= len; i++)
		cin >> s[i];
	SA();
	Getheight();
	int front=1, rear=len;
	int maxlen = 0;
	while (front <= rear)//二分法!
	{
		int mid = (front + rear) / 2;
		if (check(mid))
		{
			front = mid + 1;
			maxlen = mid;
		}
		else
			rear = mid - 1;
	}
	cout << maxlen << endl;
}

评测结果:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值