现在有个问题需要我们去解决,这个问题是这样的:
有一个字符串,这个字符串的首尾是连在一起的,要求寻找一个位置,使得以该位置为起点的字符串的字典序在所有的字符串中最小。
最最暴力的做法就是用O(n)的时间枚举起始位置,O(n)的时间比对字符串的字典序,总的时间复杂度是O(n2),别想了,这种做法肯定不行。
那我们再来看一个稍微朴素点的算法(看的时候最好自己在草稿纸上模拟一下)
我们设计i,j两个指针。其中i指向最小表示的位置,j作为比较指针。
初始时令i = 0,j = 1
如果S[i] > S[j] 则i = j++
如果S[i] < S[j] 则j++
如果S[i]==S[j] 设指针k,分别从i和j位置向下比较,直到S[i+k] != S[j+k]
如果S[i+k] > S[j+k] 则i = j++
否则 j += k+1
返回i
注意到,朴素算法的缺陷在于斜体的情况下i指针的移动太少了。针对这一问题改进就得到了最小表示法的算法。最小表示法的算法思路是维护两个指针i,j。
初始时令i=0,j=1
如果S[i] > S[j] 则i = j,j = i+1
如果S[i] < S[j] 则j++
如果S[i]==S[j] 设指针k,分别从i和j位置向下比较,直到S[i+k] != S[j+k]
如果S[i+k] > S[j+k] i += k+1
否则j += k+1
返回i和j的较小者
注意到上面两个算法唯一的区别是我用荧光笔刷出的那一行。这一行就把复杂度降到O(n)了。
为什么会这样呢?其实我开始看半天也不知道为什么会这样,我后来测试了一下,对于同一道题目,第使用一种算法用了625ms,而第二种只用了16ms!!你就知道差别有多大了。
按我的理解来说,第一种算法仅仅是用i维护了最小表示的位置,j不断往后进行比较;而第二种算法是比较完S[i+k]和S[j+k]后根据情况来判断i和j谁才是最小表示位置,对应的另外一个值去进行更新比较,这样i和j都能用来维护了最小表示的位置,效率会高上很多,妙啊~这种思想好像在哪儿见过,具体的已经忘了,不过以后碰到了理解起来也会更加容易
值得一提的是,可以应用最小表示法判断两个字符串是否同构,与KMP类似,最小表示法处理的是一个字符串S的性质,而不是看论文时给人感觉的处理两个字符串。 只要将两个串的最小表示求出来,然后从最小表示开始比较。剩下的工作就不用多说了。
Code
int Get_min() { int n = strlen(s); int i = 0, j = 1, k = 0, t; //表示从i开始k长度和从j开始k长度的字符串相同 while(i<n && j<n && k<n) { t = s[(i+k)%n] - s[(j+k)%n]; //t用来计算相对应位置上那个字典序较大 if(!t) k++;//字符相等的情况 else { if(t>0) i += k+1; //i位置大 else j += k+1; //j位置大 if(i==j) j++; k = 0; } } return i >j ?j :i; }
参考文章:
https://blog.csdn.net/li1615882553/article/details/80136776
https://wenku.baidu.com/view/afcc12d4b14e852458fb571b.html
https://www.cnblogs.com/XGHeaven/p/4009210.html