【我的板子】前缀函数

一、点击查看参考及定义

给定一个长度为 n 的字符串 s,其前缀函数被定义为一个长度为 n 的数组 pi 。
主要就是来求一个字符串从第一个字符开始的每一个子串的前缀后缀相等的最长长度。
比如:求abcab的前缀函数求得就是
a的前缀后缀最长的长度:1
ab的前缀后缀最长的长度:0
abc的前缀后缀最长的长度:0
abca的前缀后缀最长的长度:1
abcab的前缀后缀最长的长度:2
所以要求一个字符串的前缀后缀相等的最长的长度可以用前缀函数

二、计算前缀函数的朴素算法

只需要了解s.substr(0, j)是用来截取s字符串的从下标为0的字符开始的j个字符的,下面的代码就很好理解,至于为什么第二个循环是从i开始呢,是因为遍历到i时,也就是要求0-i这段字符串的最长公共前后缀的长度,那它最大就是i

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = i; j >= 0; j--)  // improved: j=i => j=pi[i-1]+1
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

三、前缀函数的第一个优化

这里跟朴素算法的区别是进行了范围缩小,我们可以利用0~i-1这段字符串来推出0~i这段字符串的最长公共前后缀。
比如:
下标: 0 1 2 3 4 5
字符串:a b c a b c
比如我们现在要求i=5的pi值,那么这个值要么比pi[4]大一(abcabc),要么减少(abdabc、ababa),要么维持不变(aabaaa)所以他的最大值是pi[4]
+1

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++)
    for (int j = pi[i - 1] + 1; j >= 0; j--)  // improved: j=i => j=pi[i-1]+1
      if (s.substr(0, j) == s.substr(i - j + 1, j)) {
        pi[i] = j;
        break;
      }
  return pi;
}

四、第二个优化

我们发现前两个算法中枚举最长长度j每次是递减1的,但其实不用,我们可以发现如下图所示,要求第i+1个pi值,我们首先从pi[i]+1开始,如果s[pi[i]]即图中s4等于s[i+1]的话,pi[i+1]就等于pi[i]+1,但是如果不行等的话,我们也是没必要逐一递减的,假设j是0~i的第二最长公共前后缀,那么我们只需要去比较s[j]和s[i+1],这样我们就会发现我们其实可以跳着递减j去枚举

在这里插入图片描述

vector<int> prefix_function(string s) {
  int n = (int)s.length();
  vector<int> pi(n);
  for (int i = 1; i < n; i++) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];
    if (s[i] == s[j]) j++;
    pi[i] = j;
  }
  return pi;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值