最小表示法 - 一个循环串中,字典序最小的串

最小表示法使得一个环形字符串有唯一的读法,这个读法是所有读法中字典序最小的。

什么意思呢?

给你一个字符串 :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;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值