最小循环子数组

一、题目描述

给定一个由若干整数组成的数组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次拼接而成 |

四、解题思路

  1. 核心思想

利用KMP 算法的前缀表(next 数组) 找数组的 "最长相同前后缀",通过数学推导得出最小重复子串长度:

  • 最长相同前后缀的本质是 "数组开头和结尾重复的部分";
  • 若数组能被某个子串重复拼接而成,那么 "数组长度 - 最长相同前后缀长度" 就是这个子串的最小长度;
  • 例如数组[1,2,1,2],最长相同前后缀长度是 2(前缀[1,2]、后缀[1,2]),数组长度 4 - 2 = 2,即最小重复子串长度为 2。
  1. 问题本质分析

这个问题的核心是找数组的周期规律

  • 数组的 "周期" = 数组长度 - 最长相同前后缀长度;
  • 若数组长度能被周期整除,说明数组是由 "周期长度的子串" 重复拼接而成(周期就是最小重复子串长度);
  • 若不能整除,说明数组无周期规律,最小重复子串就是数组本身。

举个例子理解 "最长相同前后缀":

  • 数组[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。
  1. 核心逻辑

  2. 前缀表(next 数组)构建:遍历数组,通过 "匹配则延长、不匹配则回退" 的规则,计算每个位置的最长相同前后缀长度;

  3. 推导周期:用数组总长度减去 "整个数组的最长相同前后缀长度",得到候选周期;

  4. 验证周期:若数组长度能被候选周期整除,候选周期就是最小重复子串长度;否则最小长度为数组本身;

  5. 输出结果:截取对应长度的子数组并拼接为字符串。

  6. 步骤拆解

  7. 输入处理:读取数组长度和数组元素,转为 int 数组;

  8. 构建 next 数组

    • 初始化j=1(遍历指针)、k=0(前缀指针);
    • 遍历数组:
      • nums[j] == nums[k]next[j] = k+1j++k++
      • 若不相等且k>0k回退到next[k-1]
      • 若不相等且k=0j++
  9. 计算最小重复子串长度

    • 获取整个数组的最长相同前后缀长度m = next[n-1]
    • 计算候选周期cycle = n - m
    • n % cycle == 0,最小长度为cycle;否则为n
  10. 输出结果 :截取数组前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;
  }
}
相关推荐
追随者永远是胜利者3 分钟前
(LeetCode-Hot100)301. 删除无效的括号
java·算法·leetcode·职场和发展·go
追随者永远是胜利者8 分钟前
(LeetCode-Hot100)239. 滑动窗口最大值
java·算法·leetcode·职场和发展·go
今心上8 分钟前
spring中的@Autowired到底是什么
java·后端·spring
im_AMBER11 分钟前
Leetcode 126 两数之和 II - 输入有序数组 | 盛最多水的容器
数据结构·学习·算法·leetcode
ShiJiuD66688899915 分钟前
Java 异常 File
java·开发语言
lxl130721 分钟前
C++算法(5)位运算
java·c++·算法
tankeven21 分钟前
HJ96 表示数字
c++·算法
嵌入式×边缘AI:打怪升级日志24 分钟前
C语言算术赋值运算复习笔记
c语言·stm32·单片机·算法·51单片机·proteus·代码
lxl130727 分钟前
C++算法(4)前缀和
开发语言·c++·算法
不想看见40437 分钟前
Minimum Path Sum 基本动态规划:二维--力扣101算法题解笔记
算法·leetcode·动态规划