LeetCode 1566. 重复至少 K 次且长度为 M 的模式
题目描述


题意分析
在数组任意位置,寻找长度为
m的一段模式连续重复至少k次。相邻重复块首尾相接,不能重叠,也不能在块之间夹入其他元素;因此至少需要连续覆盖m * k个元素。题目保证
k >= 2。模式的内容和起点都未知,但相邻两个重复块的对应元素一定相距m,可以直接利用这个固定间隔比较。
解法:固定偏移的连续匹配
核心思路
[!blue]
从下标
m开始,逐个比较arr[i]与arr[i - m]。若某段由k个相同块组成,从第二块开始的每一个位置都应等于前一块的对应位置,共需要m * (k - 1)次连续成功比较。反过来,如果连续这么多次比较都成功,那么这段比较连同前面的首块正好覆盖
m * k个元素。每个位置都等于向前偏移m的位置,逐块传递后,全部k个块都等于首块,因此这个条件也足以证明模式存在。用
matched记录当前结尾连续成功的比较次数。相等就加一,不相等就清零;达到阈值立即返回true。失配后重新累计,可以寻找从更后位置开始的模式,而无需预先枚举起点。扫描步长是
1,不是m,所以模式可以从任意下标开始。达到恰好k次重复已经满足“至少”的要求,不必继续查找是否还能扩展。
解题步骤
- 若
m * k超过数组长度,直接返回false。- 令所需连续比较次数
needed = m * (k - 1),当前次数matched = 0。- 从下标
m开始逐位置比较相距m的两个元素:相等就增加matched,失配就清零。- 若
matched >= needed,当前结尾已经形成至少k个相同的非重叠块,返回true。- 扫描完成仍未达到阈值,返回
false。
代码实现
class Solution {
public boolean containsPattern(int[] arr, int m, int k) {
if (m * k > arr.length) {
return false;
}
// 首块之后还需连续匹配其余块的所有位置。
int needed = m * (k - 1);
int matched = 0;
for (int i = m; i < arr.length; i++) {
if (arr[i] == arr[i - m]) {
matched++;
} else {
// 失配必须清零,不能拼接不连续的匹配次数。
matched = 0;
}
if (matched >= needed) {
return true;
}
}
return false;
}
}
func containsPattern(arr []int, m int, k int) bool {
if m*k > len(arr) {
return false
}
// 首块之后还需连续匹配其余块的所有位置。
needed := m * (k - 1)
matched := 0
for i := m; i < len(arr); i++ {
if arr[i] == arr[i-m] {
matched++
} else {
// 失配必须清零,不能拼接不连续的匹配次数。
matched = 0
}
if matched >= needed {
return true
}
}
return false
}
复杂度分析
- 时间复杂度:$O(n)$,扫描一次数组。
- 空间复杂度:$O(1)$,只保存连续匹配次数。
关键点总结
[!green]
matched统计连续成功的逐元素比较次数,既不是元素总数,也不是完整块数。- 第一块不需要与更早位置比较,因此只需检查后面
k - 1块的所有位置。- 固定偏移负责保证块内容相同,比较结果的连续性负责保证重复块首尾相接。
易错点总结
[!yellow]
- 失配必须清零,不能把中间断开的成功次数拼成一个重复模式。
- 不能只从
m的整数倍位置开始检查,合法起点不要求与数组开头对齐。- 阈值是
m * (k - 1),写成m * k会额外要求一整块的比较。- 重复块不能重叠;数组总长度不足
m * k时,即使含有相同值也不能满足要求。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 459. 重复的子字符串 | 简单 | 同样检测连续重复模式,原题要求覆盖整个字符串,本题寻找任意位置的固定长度重复块。 |
| 1668. 最大重复子字符串 | 简单 | 原题模式内容已知并求最大重复次数,本题只给模式长度并判断是否至少重复k次。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!