最小表示法

循环同构

两个字符串,如果可以从任意一个点开始,从这个点到尾,再从头到这个点,相同,就称之为循环同构
eg.
abbaa 和 baaab 就是循环同构

最小表示法

在字符串s的循环同构中,必有一个字典序最小,称之为字符串s的最小表示法
eg.
s = abbaa,s的最小表示法是 aaabb

算法0

把s的全部最小表示法枚举出来,然后比较

算法1

先把s 复制一遍 变成 s+s,然后用两个指针i,j来指,如果然后比较s[I], s[j],的大小,大的就往后指
eg.
s = abbaa
a = abbaaabbaa
I = 0, j = 1
step 0 a[0] < a[1] j++
step 1 a[0] < a[2] j++
step 3 a[0] == a[3] 此时比较 a[0+1] a[3+1], b > a, 所以 I++
step 4 a[1] > a[3] I++
step 5 a[2] > a[3] I++ 此时 I == j,所以I再++
step 6 a[3] == a[4] 比较 a[3+1] a[4+1] 又等,比较a[3+2],a[4+2], a[5] < a[6] i++
……当i>s.size()时,就重复了,不用找了

算法2 正确算法

对算法1进行优化,每次i, j 不是移动一位,而是移动k位
证明:
当a[i + k] > a[j + k] 时,i 直接 移动到 i + k + 1
在a[i] 到 a[i + k] 这段,设x 有 0<x<k a[i + x] == a[j + x] 因为a[i + k] > a[j + k],所以以a[i + x]打头的循环节 必定 大于 a[j + x]的字符串,这样的话要是最小表示也是 j + x,i + x已经没有机会了

Code

JAVA

mini函数 接受一个String 返回String的最小表示
注意JAVA里的String.substring参数是开头结尾 和 C++中 string.substr参数是开头,长度不同
此外JAVA只能charAt 不能 []

public static String mini(String s) { s = s + s; boolean flag = false; int i = 0, j = 1, k, l = s.length() / 2, p = 0; while (i < l && j < l) { k = 0; while (s.charAt(i + k) == s.charAt(j + k) && k < l) k++; if (k == l) { p = i; flag = true; break; }
      if (s.charAt(i + k) > s.charAt(j + k))
        if (i + k + 1 > j)
          i = i + k + 1;
        else i = j + 1;
      else
      if (j + k + 1 > i) j = j + k + 1; else j = i + 1;
    }
    if (!flag)
      if (i < j) p = i; else p = j;
    return s.substring(p, p + l);
  }

C++

      string ss;
      cin >> ss;
      ss = ss + ss;
      bool flag = false;
      int i = 0, j = 1, k = 0, l = ss.size() / 2, p = 0;
      while (i < l && j < l)
      { k = 0; while (ss[i + k] == ss[j + k] && k < l) k++; if (k == l) { p = i; flag = true; break; }
        if (ss[i + k] > ss[j + k])
          if (i + k + 1 > j) i = i + k + 1; else i = j + 1;
        else
          if (j + k + 1 > i) j = j + k + 1; else j = i + 1;
      }
      if (!flag)
      if (i < j) p = i; else p = j;
      ss = ss.substr(p, l);
    }

参考资料

1
2

你可能感兴趣的:(最小表示法)