【算法】【字符串】最小表示法

相关概念:

  • 在介绍后续算法前,首先要理解循环同构的概念。
    • 定义太拗口了,可以直接看例子:字符串 abcd 的循环同构有 bcdacdabdabc
  • 最小表示法是用来解决字符串最小表示问题的方法。
    • 而字符串的最小表示指的是字符串和它的所有循环同构中字典序最小的字符串。
  • 算法最后只需要返回循环同构中字典序最小的字符串的起始位置即可。

核心思路:

  • 可以把字符串看作是可循环的字符串,通常循环串的题目,都可以对循环串进行相应的处理:复制一倍
    • 如字符串 abcd 复制一倍即变成 abcdabcd,如此在新的字符串中就可以访问到原字符串的所有循环同构。
    • 实际代码中不需要真的开辟两倍的数组空间,只需要通过取模运算进行访问数组即可。【假设访问循环串 S 的当前索引为 i,而 i > n,其中 n 为字符串大小,则可以通过 S[i%n] 来访问到对应字符】
  • 最小表示法的暴力解法很直观,因为已经进行复制一倍的操作,所以循环字符串的所有可能性都可以线性访问。
    • 则可以维护三个变量 ijkij 表示两个字符串的起始索引,k 表示两个字符串同步访问的距离。
    • 暴力解法只需要以 O(n) 的时间复杂度遍历所有的两对字符串,再在每一次遍历中以 O(n) 的时间复杂度对比两个字符串的字典序,对比完成后保留字典序更小的字符串起始位置即可(另一个位置向后移动)。
  • 可以从具体例子中理解暴力法的遍历过程:
    1. 假设字符串 sabcd,字符串长度 n = 4,且初始化 i = 0j = 1k = 0
      最小表示法

    2. 移动 k 之后,此时 i = 0j = 1k = 1
      最小表示法_2

    3. 在上一步的比较结束后,保留字典序更小的字符串的起始位置 i,而将第二个字符串的起始位置 j 向后移动,此时需要进行下一次比较,因而重置 k = 0
      最小表示法_3

    4. 后续的遍历同理,所有遍历完之后返回 min(i, j) 即可。

  • 上述暴力解法其实是 O(n^2) 的时间复杂度,但是整个过程中有些比较可以避免,经过优化可以使得时间复杂度降为 O(n)
    • 考虑以下的情况,字符串 sacacab,且 i = 0j = 2k = 3,在本次比较完成后对 i 更新为 i + k + 1 以进行下一次比较(而在暴力法中会将 i 更新为 i + 1):
      最小表示法_4

    • 优化的关键在于此:当以 i 为起始位置字符串的字典序更大时,会将 i 更新为 i + k + 1,因为所有以 i + p 为起始位置的字符串均无需再进行比较p 的取值范围为 [0, k])。

      • i + p 为起始位置的字符串,一定存在以 j + p 为起始位置的字符串比它更优。
    • 事实上还避免了很多其他情况,但上述情况已经可以说明优化后解法的正确性。

算法步骤:

  • 优化后解法步骤如下:
    1. 将字符串复制一倍。【此操作非必要】
    2. 初始化指针 i = 0j = 1,初始化匹配长度 k = 0
    3. 根据匹配位置 k 进行比较,根据比较结果移动相应指针,同时重置匹配长度 k;若指针移动后,两指针相同,则移动指针 j
    4. 重复上一步骤直到比较结束。
    5. 最后的结果为 min(i, j)。【返回结果是字符串的起始索引】

代码实现:

int minimal_representation(string& s)
{
  int n = s.size();
  int k = 0, i = 0, j = 1;
  while (k < n && i < n && j < n)
  {
    if (s[(i + k) % n] == s[(j + k) % n]) ++k;
    else
    {
      s[(i + k) % n] > s[(j + k) % n] ? i = i + k + 1 : j = j + k + 1;
      if (i == j) j++;
      k = 0;
    }
  }
  i = min(i, j);
  return i;
}

部分题目:

  • leetcode - 796.旋转字符串【比较两个字符串的最小表示结果是否相等】
  • leetcode - 899.有序队列
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值