• 字符串的最小表示法


    “最小表示法”思想的提出

    首先来看一个引例:

    [引例]有两列数,a1,a2,a3 .....an 和b1,b2,b3..... bn ,不记顺序,判断它们是否相同。

    [分析]由于题目要求“不记顺序”,因此每一列数的不同形式高达n!种之多!如果要一一枚举,显然是不科学的。

    于是一种新的思想提出了:如果两列数是相同的,那么将它们排序之后得到的数列一定也是相同的。算法复杂度迅速降低为O(Nlog2N)。

    这道题虽然简单,却给了我们一个重要的启示:

    当某两个对象有多种表达形式,且需要判断它们在某种变化规则下是否能够达到一个相同的形式时,可以将它们都按一定规则变化成其所有表达形式中的最小者,然后只需要比较两个“最小者”是否相等即可!

    最小表示法是求与某个字符串循环同构的所有字符串中,字典序最小的串是哪个

    循环同构
    字符串S = “bacda”,它的循环同构"acdab",“cdaba”,“dabac”,“abacd”.

    最小表示
    字符串和它的所有循环同构中字典序最小的字符串。S = “bacda"的最小表示"abacd”

    这几个串在原串上的开始位置分别是0,1,2,3,4。

    默认从0开始比较方便,这一点之后也会再提到。

    暴力方法很简单,把所有的都列出来再排个序就行了。

    暴力的时间复杂度是很高的,然而我们可以做到O(n)求出字典序最小的串的开始位置。

    大致思路如下:

    设i、j是两个“怀疑是最小的位置”,比如说如果你比较到了bacda的两个a,你目前还不知道从哪个i开始的字符串是最小的。

    设k表示,从i往后数和从j往后数,有多少是相同的。

    开始时先设i=0,j=1,k=0。

    每次都对i+k、j+k进行一次比较。

    发现i+k有可能大于字符串长度n啊,怎么办呢?

    首先想到将字符串倍长:bacdabacda。

    但是这样做很麻烦,而且倍长之后,前后两段都是完全一样的。

    所以我们只需要取模n就好了:(i+k)%n。

    这么做就要求字符串从0开始,如果从1开始的话,就有点麻烦了,还得+1-1什么的,不如从0开始简单明了。

    比较完i+k和j+k,如果两个字符相等,那么显然k++。

    如果不相等,那么哪边比较大,哪边就肯定不是最小的了,同时把k重置为0。

    如果出现了i、j重合的情况,把j往后移动一位。

    最后输出i、j较小的那个就好了。

    什么,想看具体点的,那接着往下看吧

    定义三个指针,i,j,k

    初始i=0;  j=1;  k=0;

    首先,如果s[i]<s[j]那么很明显j++

    如果s[i]>s[j]那么也很明显i=j++

    剩下的就是如果s[i]==s[j]的时候。

    这时候有一个性质就是在i和j之间的所有的字符一定是大于等于s[i]的

    令k=0,循环寻找第一个s[i+k]!=s[j+k]的位置

    如果s[i+k]<s[j+k]那么j+=k+1

    为什么呢?

    首先s[i]到s[i+k-1]一定是大于等于s[i],因为如果其中有一个数小于s[i],那么这个数一定在s[j]到s[j+k-1]中存在,又因为必定有一个会在后面,所以如果s[j]先碰到了,那么一定不会继续到k的位置的,所以一定不存在比s[i]小的字符。

    所以从其中的任意一个字符开始当作起始点,都不会比现在更小,所以只有从选出来的序列的后面那一个字符开始才有可能会是最小。

    所以j+=k+1

    如果序列中某个数和s[i]相等的话,那么一定会有之前或者以后再这个位置起始过,所以不需要再从这个位置进行起始。

    因为在这里i和j是等价的,i在前和j在前的结果是一样的,所以i和j的处理是相同的,下面就不仔细的进行讲解了。

    还有就是如果i==j那么让j++就可以回到原先的状态了

    最后的时候,肯定是小的不会动,而大的会不停的向后移动,所以最后只需要输出i和j最小的一个即可

    再从头过一遍思路:

    假设有两个下标i,j,表示如果从i和从j出发的字符串,有一个k表示字符串的长度,如果长度达到len,就表示找到最小的串。

    s[i+k] == s[j+k]:  k++

    s[i+k]>s[j+k]: i=i+k+1 表示以i,到i+k为起点的字符串,都不是最小字符串的前缀。

    s[i+k]<s[j+k]: j=j+k+1 同理

    注意:

    • i和j一定是不同的。
    • 每次不等时,需要设置k为0。
    • 循环条件是小于不是小于等于
     1 int getmin(char *str)
     2 {
     3     int len=strlen(str);
     4     int i=0,j=1,k=0;
     5     while(i<len&&j<len&&k<len)
     6     {
     7         int t=str[(i+k)%len]-str[(j+k)%len];
     8         if(!t) k++;
     9         else
    10         {
    11             if(t>0) i=i+k+1;
    12             else j=j+k+1;
    13             if(i==j) j++;
    14             k=0;
    15         }
    16     }
    17     return min(i,j);
    18 } 

    如果遇到了bcdefgabcdefg这样的字符串,上述的写法就有点不太好用了

    下面介绍一种改进的算法:(注意理解加粗字体)

    初始i = 0, j = 1, k = 0. i 指向第一个字符,j 指向第二个字符,k 表示增量。
    每次比较 t = s[(i+k) % len] - s[(j+k) % len]
    t == 0 表示从i,j开始长度为(k + 1)的字符串相同, 所以更新k++。
    t < 0 表示从i 开始长度为(k + 1)的字符串的字典序小于从 j 开始, 所以更新j = max(j+k+1, i+1)。
    t > 0 表示从i 开始长度为(k + 1)的字符串的字典序大于从 j 开始, 所以更新i = max(i+k+1, j+1)。
    当k更新到len次或者i,j超出长度。就能确定一个位置min(i, j)
    为什么更新i,j 用max?例如移动 i 时,正常更新是向后移动k位,但是如果移动之后还是小于j,完全可以移动到j +1的位置。因为i, j 之间的字符肯定大于j。

     1 int MinRepresent(char *s)
     2 {
     3     int i = 0, j = 1, k = 0;
     4     int len = strlen(s);
     5     while (i < len && j < len && k < len) {
     6         int t = s[(i+k) % len] - s[(j+k) % len];
     7         if (t == 0) k++;
     8         else{
     9             if (t < 0)  j = max(j+k+1, i+1);
    10             else i = max(i+k+1, j+1);
    11             k = 0;
    12         }
    13     }
    14     return min(i, j);
    15 }

    一些应用

    求字符串的最小表示法

    求最小周期长度

    求最大表示法

    总结(摘自周源《浅析“最小表示法”思想在字符串循环同构问题中的应用》):

    最小表示法是一个与匹配算法本质不同的线性算法。“最小表示法”思想引导我们从问题的另一方面分析,进而构造出一个全新的算法。

    比起匹配算法,它容易理解,便于记忆,实现起来,也不过是短短的几重循环,非常方便,便于应用于扩展问题

    “最小表示法”是判断两种事物本质是否相同的一种常见思想,它的通用性也是被人们认可的——无论是搜索中判重技术,还是判断图的同构之类复杂的问题,它都有着无可替代的作用。仔细分析可以得出,其思想精华在于引入了“序”这个概念,从而将纷繁的待处理对象化为单一的形式,便于比较。

    然而值得注意的是,在如今的信息学竞赛中,试题纷繁复杂,使用的算法也不再拘泥于几个经典的算法,改造经典算法或是将多种算法组合是常用的方法之一。单纯的寻求字符串的最小表示显得得不偿失,但利用“最小表示法”的思想,和字符串的最小表示这个客观存在的事物,我们却找到了一个简单、优秀的算法。

    因此,在解决实际问题时,只有深入分析,敢于创新,才能将问题化纷繁为简洁,化无序为有序。


    我在学习该算法时阅读了许多博客,上文是我借鉴它们总结的一篇文章,以便自己以后查阅用的

    下面是原文地址:

    https://www.cnblogs.com/eternhope/p/9972846.html

    https://www.cnblogs.com/XGHeaven/p/4009210.html

    https://blog.csdn.net/henuyh/article/details/85868528

  • 相关阅读:
    have you declared this activity in your AndroidManifest.xml?
    Android收回输入法的实现
    Android手机Home键重写
    Android屏幕点击事件重写
    拖动ListView列表时背景变黑
    AFNetworking vs ASIHTTPRequest vs MKNetworkKit
    libgif.so
    android.support.v4.widget.DrawerLayout
    Titanium vs PhoneGap
    Non-constant Fields in Case Labels
  • 原文地址:https://www.cnblogs.com/jiamian/p/11236830.html
Copyright © 2020-2023  润新知