题解:AtCoder AT_awc0045_b Order of Books on the Bookshelf

【题目来源】

AtCoder:B - Order of Books on the Bookshelf

【题目描述】

Takahashi's bookshelf has \(N\) books lined up in a row. The \(i\)-th book from the left has a number written on it represented by the integer \(A_i\).
高桥的书架上有一排 \(N\) 本书。从左数第 \(i\) 本书上写着一个整数 \(A_i\)

Aoki saw \(M\) books lined up in a row in a bookstore's show window. The \(j\)-th book from the left has a number written on it represented by the integer \(B_j\).
青木在书店的橱窗中看到一排 \(M\) 本书。从左数第 \(j\) 本书上写着一个整数 \(B_j\)

Note that book numbers may have duplicates.
注意,书的编号可能有重复。

Determine whether there exists a contiguous subsequence in Takahashi's bookshelf that exactly matches the arrangement in the show window.
判断在高桥的书架中是否存在一个连续子序列,恰好与橱窗中的排列相匹配。

Specifically, determine whether there exists an integer \(i\) (\(1 \le i \le N - M + 1\)) such that \(A_i, A_{i+1}, \dots, A_{i+M-1}\) match \(B_1, B_2, \dots, B_M\) respectively. If such an \(i\) exists, output the smallest such \(i\). If it does not exist, output \(-1\).
具体来说,判断是否存在一个整数 \(i\)\(1 \le i \le N - M + 1\)),使得 \(A_i, A_{i+1}, \dots, A_{i+M-1}\) 分别与 \(B_1, B_2, \dots, B_M\) 相匹配。如果存在这样的 \(i\),输出最小的 \(i\)。如果不存在,输出 \(-1\)

【输入】

\(N\) \(M\)
\(A_1\) \(A_2\) \(\dots\) \(A_N\)
\(B_1\) \(B_2\) \(\dots\) \(B_M\)

  • The first line contains the number of books on Takahashi's bookshelf \(N\) and the number of books in the show window \(M\), separated by a space.
  • The second line contains the numbers of each book on Takahashi's bookshelf \(A_1, A_2, \dots, A_N\), separated by spaces.
  • The third line contains the numbers of each book in the show window \(B_1, B_2, \dots, B_M\), separated by spaces.

【输出】

Output in one line the smallest integer \(i\) such that \(A_i, A_{i+1}, \dots, A_{i+M-1}\) match \(B_1, B_2, \dots, B_M\) respectively. If no such \(i\) exists, output \(-1\).

【输入样例】

6 3
4 1 2 3 5 6
2 3 5

【输出样例】

3

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的数组 \(A\) 和长度为 \(M\) 的数组 \(B\),判断 \(A\) 中是否存在一个连续子数组与 \(B\) 完全匹配,并输出最小起始位置;若不存在则输出 \(-1\)。这是一个经典的模式匹配问题,将数组 \(B\) 视为模式串,数组 \(A\) 视为文本串,求模式串在文本串中的首次出现位置。

  2. 算法选择

    • KMP 算法:预处理 next 数组记录模式串的最长相等前后缀长度,失配时避免文本串指针回溯,在 \(O(N + M)\) 时间内完成匹配
    • 双指针暴力匹配\(O(N \cdot M)\)):枚举每个起始位置并逐位比对,思路直观但会超时,仅作为理解问题的辅助
  3. 关键步骤

    • 读取输入\(N\)\(M\)、数组 \(A[1..N]\)、数组 \(B[1..M]\)
    • 预处理 next 数组(模式串 \(B\) 的自匹配):
      • 遍历 \(i\)\(2\)\(M\)\(j = 0\)
      • \(j > 0\)\(B[i] \neq B[j+1]\) 时:\(j = \text{next}[j]\)(回退到上一个可能匹配的前缀)
      • \(B[i] == B[j+1]\)\(j\) 前进一步
      • \(\text{next}[i] = j\)(记录前缀 \(B[1..i]\) 的最长相等前后缀长度)
    • KMP 匹配(文本串 \(A\) 与模式串 \(B\) 匹配):
      • 遍历 \(i\)\(1\)\(N\)\(j = 0\)
      • \(j > 0\)\(A[i] \neq B[j+1]\) 时:\(j = \text{next}[j]\)
      • \(A[i] == B[j+1]\)\(j\) 前进一步
      • \(j == M\):完全匹配成功,输出起始位置 \(i - M + 1\)
    • 未找到:输出 \(-1\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N + M)\)。预处理 next 数组 \(O(M)\),KMP 匹配 \(O(N)\),文本串指针永不回溯
    • 空间复杂度:\(O(N + M)\),存储两个数组和 next 数组
  5. KMP 的核心思想

    • 避免回溯:文本串指针 \(i\) 只向前移动,失配时通过 next 数组调整模式串指针 \(j\),而非将 \(i\) 回退到起始位置的下一个字符
    • 最长相等前后缀next[i] 表示模式串前缀 \(B[1..i]\) 中,相等的真前缀与真后缀的最大长度。失配时,已匹配的部分可直接复用,跳到下一个可能匹配的位置
    • 自匹配构建:模式串与自身进行 KMP 匹配来构建 next 数组,利用相同逻辑避免重复计算
    • 适用于单模式串在文本串中的精确匹配、重复子串检测、循环节分析等问题

【算法标签】

KMP

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int a[N], b[N];
vector<int> v;  // 这个vector声明了但没有使用

int main() {
    cin >> n >> m;  // 输入数组a和b的长度

    // 输入数组a
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    // 输入数组b
    for (int i = 1; i <= m; i++)
        cin >> b[i];

    int st = 1;  // 起始位置
    // 遍历所有可能的起始位置
    while (st <= n - m + 1) {
        int i = st, j = 1;
        // 尝试匹配
        while (j <= m && a[i] == b[j]) {
            i++;
            j++;
        }
        // 如果b的所有元素都匹配成功
        if (j > m) {
            cout << st << endl;  // 输出起始位置
            return 0;
        }
        st++;  // 尝试下一个起始位置
    }

    // 没有找到匹配
    cout << -1 << endl;
    return 0;
}
// 使用双指针算法会超时,改用KMP
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
int p[N], s[N];  // p: 模式串, s: 文本串
int ne[N];       // KMP的next数组

int main() {
    cin >> n >> m;  // 输入文本串长度n和模式串长度m

    // 输入文本串s
    for (int i = 1; i <= n; i++)
        cin >> s[i];

    // 输入模式串p
    for (int i = 1; i <= m; i++)
        cin >> p[i];

    // 1. 构建KMP的next数组
    for (int i = 2, j = 0; i <= m; i++) {
        // 当不匹配时,回退到next[j]
        while (j && p[i] != p[j + 1]) j = ne[j];
        // 如果匹配,j向前移动
        if (p[i] == p[j + 1]) j++;
        ne[i] = j;  // 记录next值
    }

    // 2. KMP匹配
    for (int i = 1, j = 0; i <= n; i++) {
        // 当不匹配时,利用next数组回退
        while (j && s[i] != p[j + 1]) j = ne[j];
        // 如果匹配,j向前移动
        if (s[i] == p[j + 1]) j++;
        // 完全匹配成功
        if (j == m) {
            printf("%d ", i - m + 1);  // 输出起始位置
            return 0;
        }
    }

    // 没有找到匹配
    cout << -1 << endl;
    return 0;
}

【运行结果】

6 3
4 1 2 3 5 6
2 3 5
3
posted @ 2026-06-21 11:25  团爸讲算法  阅读(8)  评论(0)    收藏  举报