旋转后字典中最小的字符串

lexicographically smallest string after rotation

本文关键字:字符串 字典 旋转      更新时间:2023-10-16

我正试图在spoj 中解决这个问题

我需要找到一个给定字符串的旋转次数,使其在所有旋转中在字典上最小。

例如:

原件:ama

第一轮:maa

第二次旋转:aam这是字典上最小的旋转,所以答案是2。

这是我的代码:

string s,tmp;
    char ss[100002];
    scanf("%s",ss);
    s=ss;
    tmp=s;
    int i,len=s.size(),ans=0,t=0;
    for(i=0;i<len;i++)
    {
        string x=s.substr(i,len-i)+s.substr(0,i);
        if(x<tmp)
        {
            tmp=x;
            t=ans;
        }
        ans++;
    }
    cout<<t<<endl;

我收到了此解决方案的"超过时间限制"。我不明白可以做什么优化。如何提高解决方案的速度?

您可以使用修改后的后缀数组。我的意思是修改,因为你不能停留在词尾。

以下是我解决的类似问题的代码(SA是后缀数组):

//719
//Glass Beads
//Misc;String Matching;Suffix Array;Circular
#include <iostream>
#include <iomanip>
#include <cstring>
#include <string>
#include <cmath>
#define MAX 10050
using namespace std;
int RA[MAX], tempRA[MAX];
int SA[MAX], tempSA[MAX];
int C[MAX];                
void suffix_sort(int n, int k) {
    memset(C, 0, sizeof C);        
    for (int i = 0; i < n; i++)        
        C[RA[(i + k)%n]]++;
    int sum = 0;
    for (int i = 0; i < max(256, n); i++) {                     
        int t = C[i]; 
        C[i] = sum; 
        sum += t;
    }
    for (int i = 0; i < n; i++)        
        tempSA[C[RA[(SA[i] + k)%n]]++] = SA[i];
    memcpy(SA, tempSA, n*sizeof(int));
}
void suffix_array(string &s) {             
    int n = s.size();
    for (int i = 0; i < n; i++) 
        RA[i] = s[i];              
    for (int i = 0; i < n; i++) 
        SA[i] = i;
    for (int k = 1; k < n; k *= 2) {     
        suffix_sort(n, k);
        suffix_sort(n, 0);
        int r = tempRA[SA[0]] = 0;
        for (int i = 1; i < n; i++) {
            int s1 = SA[i], s2 = SA[i-1];
            bool equal = true;
            equal &= RA[s1] == RA[s2];
            equal &= RA[(s1+k)%n] == RA[(s2+k)%n];
            tempRA[SA[i]] = equal ? r : ++r;     
        }
        memcpy(RA, tempRA, n*sizeof(int));
    } 
}
int main() {
    int tt; cin >> tt;
    while(tt--) {
        string s; cin >> s;
        suffix_array(s);
        cout << SA[0]+1 << endl;
   }
}

我主要从这本书中获得了这个实现。有一个更容易编写的O(n log²n)版本,但可能对您的情况(n=10^5)不够有效。这个版本是O(n log n),它不是最有效的算法。维基百科的文章列出了一些O(n)算法,但我发现其中大多数太复杂了,无法在编程竞赛中编写。这个O(n log n)通常足以解决大多数问题。

你可以在这里找到一些解释后缀数组概念的幻灯片(来自我提到的书的作者)。

我知道这来得很晚,但在搜索该算法的更快变体时,我在谷歌上偶然发现了这一点。事实证明,在github上可以找到一个很好的实现:https://gist.github.com/MaskRay/8803371

它使用了lyndon因子分解。这意味着它会重复地将字符串拆分为按字典顺序递减的林登单词。林登词是字符串,是它们自身的最小旋转之一。以循环方式执行此操作会将字符串的lms作为最后找到的lyndon单词。

int lyndon_word(const char *a, int n)
{
  int i = 0, j = 1, k;
  while (j < n) {
    // Invariant: i < j and indices in [0,j)  i cannot be the first optimum
    for (k = 0; k < n && a[(i+k)%n] == a[(j+k)%n]; k++);
    if (a[(i+k)%n] <= a[(j+k)%n]) {
      // if k < n
      //   foreach p in [j,j+k], s_p > s_{p-(j-i)}
      //   => [j,j+k] are all suboptimal
      //   => indices in [0,j+k+1)  i are suboptimal
      // else
      //   None of [j,j+k] is the first optimum
      j += k+1;
    } else {
      // foreach p in [i,i+k], s_p > s_{p+(j-i)}
      // => [i,i+k] are all suboptimal
      // => [0,j) and [0,i+k+1) are suboptimal
      // if i+k+1 < j
      //   j < j+1 and indices in [0,j+1)  j are suboptimal
      // else
      //   i+k+1 < i+k+2 and indices in [0,i+k+2)  (i+k+1) are suboptimal
      i += k+1;
      if (i < j)
        i = j++;
      else
        j = i+1;
    }
  }
  // j >= n => [0,n)  i cannot be the first optimum
  return i;
}