一、题目描述
给定一个由若干整数组成的数组nums,请检查数组是否是由某个子数组重复循环拼接而成,请输出这个最小的子数组。
二、输入输出描述
输入描述
第一行:数组中元素个数n,1 ≤ n ≤ 100000
第二行:数组的数字序列nums,以空格分割,0 ≤ nums[i] < 10
输出描述
- 最小的子数组的元素序列,以空格分割;
备注
数组本身是其最大的子数组,循环1次可生成的自身;
三、示例
|----|--------------------------------------------------|
| 输入 | 9 1 2 1 1 2 1 1 2 1 |
| 输出 | 1 2 1 |
| 说明 | 数组[1,2,1,1,2,1,1,2,1] 可由子数组[1,2,1]重复循环3次拼接而成 |
四、解题思路
- 核心思想
利用KMP 算法的前缀表(next 数组) 找数组的 "最长相同前后缀",通过数学推导得出最小重复子串长度:
- 最长相同前后缀的本质是 "数组开头和结尾重复的部分";
- 若数组能被某个子串重复拼接而成,那么 "数组长度 - 最长相同前后缀长度" 就是这个子串的最小长度;
- 例如数组
[1,2,1,2],最长相同前后缀长度是 2(前缀[1,2]、后缀[1,2]),数组长度 4 - 2 = 2,即最小重复子串长度为 2。
- 问题本质分析
这个问题的核心是找数组的周期规律:
- 数组的 "周期" = 数组长度 - 最长相同前后缀长度;
- 若数组长度能被周期整除,说明数组是由 "周期长度的子串" 重复拼接而成(周期就是最小重复子串长度);
- 若不能整除,说明数组无周期规律,最小重复子串就是数组本身。
举个例子理解 "最长相同前后缀":
- 数组
[1,2,1,2]:- 子串
[1]:无前后缀,最长相同前后缀长度 0; - 子串
[1,2]:前缀[1]、后缀[2],无相同,长度 0; - 子串
[1,2,1]:前缀[1,2]、后缀[2,1],最长相同是[1],长度 1; - 子串
[1,2,1,2]:前缀[1,2]、后缀[1,2],长度 2; - 最终最长相同前后缀长度是 2,周期 = 4-2=2,4 能被 2 整除,最小重复子串长度为 2。
- 子串
-
核心逻辑
-
前缀表(next 数组)构建:遍历数组,通过 "匹配则延长、不匹配则回退" 的规则,计算每个位置的最长相同前后缀长度;
-
推导周期:用数组总长度减去 "整个数组的最长相同前后缀长度",得到候选周期;
-
验证周期:若数组长度能被候选周期整除,候选周期就是最小重复子串长度;否则最小长度为数组本身;
-
输出结果:截取对应长度的子数组并拼接为字符串。
-
步骤拆解
-
输入处理:读取数组长度和数组元素,转为 int 数组;
-
构建 next 数组 :
- 初始化
j=1(遍历指针)、k=0(前缀指针); - 遍历数组:
- 若
nums[j] == nums[k]:next[j] = k+1,j++、k++; - 若不相等且
k>0:k回退到next[k-1]; - 若不相等且
k=0:j++;
- 若
- 初始化
-
计算最小重复子串长度 :
- 获取整个数组的最长相同前后缀长度
m = next[n-1]; - 计算候选周期
cycle = n - m; - 若
n % cycle == 0,最小长度为cycle;否则为n;
- 获取整个数组的最长相同前后缀长度
-
输出结果 :截取数组前
len个元素,拼接为空格分隔的字符串。
五、代码实现
java
import java.util.Arrays;
import java.util.Scanner;
import java.util.StringJoiner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = Integer.parseInt(sc.nextLine());
int[] nums = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
System.out.println(getResult(n, nums));
}
public static String getResult(int n, int[] nums) {
// KMP算法 前缀表求解
int[] next = getNext(n, nums);
// 最长相同前后缀长度
int m = next[n - 1];
// 最小重复子串的长度
int len = n % (n - m) == 0 ? n - m : n;
StringJoiner sj = new StringJoiner(" ");
for (int i = 0; i < len; i++) sj.add(nums[i] + "");
return sj.toString();
}
public static int[] getNext(int n, int[] nums) {
int[] next = new int[n];
int j = 1;
int k = 0;
while (j < n) {
if (nums[j] == nums[k]) {
next[j] = k + 1;
j++;
k++;
} else {
if (k > 0) {
k = next[k - 1];
} else {
j++;
}
}
}
return next;
}
}