最小表示法使得一个环形字符串有唯一的读法,这个读法是所有读法中字典序最小的。
什么意思呢?
给你一个字符串 :1234567
他的读法有
1234567,2345671,3456712,4567123等等
字典序最小的读法很明显就是1234567
本文讲解的是最小表示法的O(n)求法。
对于这种环形问题,一个常规的做法是把它自己复制一遍,接到原串之后。问题就转换成了求在字符串s中长度为n的最小子串。
如果我们使用暴力法来进行求解,就需要一个串来保留最小串序列,然后将该序列与所有读法进行比较,遇上比他小的,就进行替换,然后再进行比较,知道选出一个最小的位置,这种情况下最坏的时间复杂度为n*s,s为字符串的长度,但是其实,对于我们一个随机的字符串来说,这样的算法其实已经非常优秀了,基本上不可能达到n*s这种复杂度。
最小表示法!
什么是最小表示法,在我们上面讲解暴力的过程中,我们依次的将原串中的各种读法与最小串序列进行比较,但是很多比较其实是完全没有必要的,我们可以直接进行排除。
假设我们现在有两个开始位置i,j在原串上。这两开始位置依次往后进行比较,两个串的前k个字符都是相同的,第k+1个字符不同了。这时候我们就可以直接将i或者j往后移动k个位置,然后两个串再进行比较(i和j串开头的串中哪个k+1的字符大·,那么就向后移动),为什么,下面告诉你答案!
再仔细思考暴力的过程。我们假设前面s[i~k]与s[j~k]都是相同的(假设s[i+k+1]<s[j+k+1]),按暴力,我们仅仅得到s[i~k+1]小于s[j~k+1],然后舍弃j,选择i。深入地再想想,s[i~k+1]与s[j~k+1]的大小关系呢?因为s[i~k]与s[j~k]是相同的,而s[i+k+1]<s[j+k+1],所以s[i~k+1]小于s[j~k+1]。所以在枚举开始位置的时候j+1到j+k这些位置我们可以直接跳过,直接从j+k+1位置又开始两个串进行比较。为什么可以这样,看图
在上图中我们的i+k位置和j+k的位置不同了,且j位置的小,然后将可以将i移动到i+k位置,因为前k-1个字符相同,而d第k个不同,所以我们知道1一定是大于4的,2一定是大于5的,3一定是大于6的,所以123开始的位置一定不会是最小的读法,所以就直接跳过了,这样就加速了
还有一个很重要的点,我在代码中进行说明了
代码实现:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define MAX 100
int main(){
char str[MAX];
scanf("%s",str);
int len = strlen(str);
int i=0,j=1,k = 0;
for(;i<len && j < len&&k<len;){
int ie = (i + k)%len;
int je = (j + k)%len;
if(str[ie] > str[je]){
i = i + k + 1;
if(i == j){
i += 1;
}
// 因为两个开始位置都向后移动
// 所以i向后移动的时候,如果i+k的值还是小于j的话
// 那么i可以直接移动到j后面,因为j之前在往后跳的时候就能保证j
// 前面的位置开始的字符串不可能是最小的,所以可以直接移动到j后面
if(i < j){
i = j + 1;
}
k = 0;
}else if(str[ie] < str[je]){
j = j + k + 1;
if(j == i){
j += 1;
}
if(j < i){
j = i + 1;
}
k = 0;
}else{
k++;
}
}
if(i > j){
for(int k=0;k<len;k++){
printf("%c",str[j]);
j += 1;
j %= len;
}
}else{
for(int k=0;k<len;k++){
printf("%c",str[i]);
i += 1;
i %= len;
}
}
}
最大最小子串结合,flag为1表示求最大,0表示求最小
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#define MAX 1000005
char str[MAX];
int next[MAX];
int main(){
while(scanf("%s",str)!=EOF){
int len = strlen(str);
int min = express(str,len,0)+1;
int max = express(str,len,1)+1;
get_next(str,len,next);
int res = len/(len - next[len]);
printf("%d %d %d %d\n",min,res,max,res);
}
}
void get_next(char *str,int len,int *next){
if(len == 0){
return ;
}
next[0] = -1;
if(len == 1){
return;
}
next[1] = 0;
if(len == 2){
return;
}
int i=2,j=0;
for(;i<=len;){
if(str[i-1] == str[j]){
next[i++] = ++j;
}else if(j > 0){
j = next[j];
}else{
next[i++] = 0;
}
}
}
int express(char *str,int len,int flag){
int i=0,j=1,k=0;
for(;i<len&&j<len&&k<len;){
int ie = (i + k)%len;
int je = (j + k)%len;
if(str[ie] == str[je]){
k++;
continue;
}
if((str[ie] > str[je] && flag) || (!flag && str[ie] < str[je])){
j = j + k + 1;
if(j <= i){
j = i + 1;
}
}else{
i = i + k + 1;
if( i <= j){
i = j + 1;
}
}
k=0;
}
return i > j ? j : i;
}