【Leetcode】28. Find the Index of the First Occurrence in a String

题目地址:

https://leetcode.com/problems/implement-strstr/

给定两个字符串 s s s p p p,判断 p p p是否是 s s s的子串,如果是则返回匹配的起始下标,否则返回 − 1 -1 1

法1:暴力法。直接枚举所有的开始位置,依次做匹配,一旦匹配成功则返回起始点下标,如果一直未成功则返回 − 1 -1 1。代码如下:

class Solution {
 public:
  int strStr(string s, string p) {
    if (s.empty() && p.empty()) return 0;

    if (p.empty()) return 0;

    // i后面要留足p的长度这么多位置
    for (int i = 0; i + p.length() - 1 < s.length(); i++) {
      // 开始从p[0]开始匹配
      int idx = 0;
      while (idx < p.length()) {
        if (s[i + idx] != p[idx]) break;
        idx++;
      }

      // 匹配完了说明找到了子串,返回起始点下标
      if (idx == p.length()) return i;
    }

    return -1;
  }
};

时间复杂度 O ( l s l p ) O(l_sl_p) O(lslp),空间 O ( 1 ) O(1) O(1)

法2:KMP。KMP算法有一个next数组的概念,首先参考https://blog.csdn.net/qq_46105170/article/details/106168535,在未改进版本的KMP中, p p p的next数组 n [ i ] n[i] n[i]表示 p [ 0 : i − 1 ] p[0:i-1] p[0:i1]中最长的相等前后缀的长度,而在改进版本中, n [ i ] n[i] n[i]则表示当 p [ i ] p[i] p[i] s [ j ] s[j] s[j]不匹配的时候, p p p里下一个要与 s [ j ] s[j] s[j]匹配的字符的下标。未改进版本的KMP中,这个下标取的是最长的相等前后缀的长度,利用这个信息去移动 p p p,可以在不错过可能解的情况下,保证移动后和 s [ j ] s[j] s[j]比较时,之前的字符已经全部相等,也就是不用再进行匹配了;但是它还可以利用一个信息,若移动后与 s [ j ] s[j] s[j]对齐的那个字符仍然与 p [ i ] p[i] p[i]相等,那么肯定会出错,还是得继续向后移,改进的KMP算法就是将这个信息加入进去。首先,若 s [ j ] ≠ p [ i ] s[j]\ne p[i] s[j]=p[i],那么下一个与 s [ j ] s[j] s[j]对齐的字符变为了 p [ n [ i ] ] p[n[i]] p[n[i]],若还不等,则对齐的是 p [ n [ n [ i ] ] ] p[n[n[i]]] p[n[n[i]]],直到相等或者变为 − 1 -1 1为止。如果我们已经知道了 p [ i ] = p [ n [ i ] ] p[i]=p[n[i]] p[i]=p[n[i]],那再次对齐就没有意义,因为还要取一次next。所以索性在一开始就”next到底“,杜绝 p [ i ] = p [ n [ i ] ] p[i]=p[n[i]] p[i]=p[n[i]]的情况发生。代码如下:

class Solution {
 public:
  int strStr(string s, string p) {
    if (p.empty()) return 0;
    // size返回的是unsigned,这里需要转为int以防出现奇怪的错误
    int n = s.size(), m = p.size();
    auto buildNext = [&] {
      vector<int> ne(m, 0);
      for (int i = 0, j = ne[0] = -1; i < m - 1;) {
        if (j == -1 || p[i] == p[j]) {
          i++;
          j++;
          // 在未改进的KMP算法中,ne[i]直接取j;
          // 在改进的算法中需要判断一下当前字符是否和p[j]相等,如果相等则还需要ne一下
          ne[i] = p[i] == p[j] ? ne[j] : j;
        } else
          j = ne[j];
      }
      return ne;
    };
    vector<int> ne = buildNext();
    int i = 0, j = 0;
    while (i < n && j < m) {
      if (j == -1 || s[i] == p[j]) {
        i++;
        j++;
      } else
        j = ne[j];
    }

    return j == m ? i - j : -1;
  }
};

时间复杂度 O ( l s + l p ) O(l_s+l_p) O(ls+lp),空间 O ( l p ) O(l_p) O(lp)

法3:字符串哈希。采用前缀哈希的方式,先算出 p p p的哈希值,然后遍历 s s s,算出每 l p l_p lp的长度的子串的哈希值,与之比较,一旦比较相等则返回匹配起始位置。代码如下:

class Solution {
 public:
  using ull = unsigned long long;
  int strStr(string s, string p) {
    int n = s.size(), m = p.size();
    if (n < m) return -1;
    ull hash = 0, P = 131, hashP = 0, pow = 1;
    for (int i = 0; i < m; i++) {
      hashP = hashP * P + p[i];
      pow *= P;
    }

    for (int i = 0; i < n; i++) {
      if (i < m)
        hash = hash * P + s[i];
      else {
        if (hash == hashP) return i - m;
        hash = hash * P + s[i];
        hash -= s[i - m] * pow;
      }
    }

    return hash == hashP ? n - m : -1;
  }
};

时间复杂度 O ( l s ) O(l_s) O(ls),空间 O ( 1 ) O(1) O(1)

下面给出一个字符串下标从 1 1 1开始的KMP代码,C++:

class Solution {
 public:
  int strStr(string s, string p) {
    int n = s.size(), m = p.size();
    s = " " + s;
    p = " " + p;

    vector<int> ne(m + 1);
    for (int i = 2, j = 0; i <= m; i++) {
      while (j && p[i] != p[j + 1]) j = ne[j];
      if (p[i] == p[j + 1]) j++;
      ne[i] = j;
    }

    for (int i = 1, j = 0; i <= n; i++) {
      while (j && s[i] != p[j + 1]) j = ne[j];
      if (s[i] == p[j + 1]) j++;
      if (j == m) return i - m;
    }

    return -1;
  }
};

时空复杂度一样。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值