题解: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
【核心思想】
-
问题分析:给定长度为 \(N\) 的数组 \(A\) 和长度为 \(M\) 的数组 \(B\),判断 \(A\) 中是否存在一个连续子数组与 \(B\) 完全匹配,并输出最小起始位置;若不存在则输出 \(-1\)。这是一个经典的模式匹配问题,将数组 \(B\) 视为模式串,数组 \(A\) 视为文本串,求模式串在文本串中的首次出现位置。
-
算法选择:
- KMP 算法:预处理
next数组记录模式串的最长相等前后缀长度,失配时避免文本串指针回溯,在 \(O(N + M)\) 时间内完成匹配 - 双指针暴力匹配(\(O(N \cdot M)\)):枚举每个起始位置并逐位比对,思路直观但会超时,仅作为理解问题的辅助
- KMP 算法:预处理
-
关键步骤:
- 读取输入:\(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N + M)\)。预处理
next数组 \(O(M)\),KMP 匹配 \(O(N)\),文本串指针永不回溯 - 空间复杂度:\(O(N + M)\),存储两个数组和
next数组
- 时间复杂度:\(O(N + M)\)。预处理
-
KMP 的核心思想:
- 避免回溯:文本串指针 \(i\) 只向前移动,失配时通过
next数组调整模式串指针 \(j\),而非将 \(i\) 回退到起始位置的下一个字符 - 最长相等前后缀:
next[i]表示模式串前缀 \(B[1..i]\) 中,相等的真前缀与真后缀的最大长度。失配时,已匹配的部分可直接复用,跳到下一个可能匹配的位置 - 自匹配构建:模式串与自身进行 KMP 匹配来构建
next数组,利用相同逻辑避免重复计算 - 适用于单模式串在文本串中的精确匹配、重复子串检测、循环节分析等问题
- 避免回溯:文本串指针 \(i\) 只向前移动,失配时通过
【算法标签】
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
浙公网安备 33010602011771号