最小循环子数组

一、题目描述

给定一个由若干整数组成的数组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;
  }
}
相关推荐
木辰風9 小时前
PLSQL自定义自动替换(AutoReplace)
java·数据库·sql
heartbeat..9 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
Prince-Peng9 小时前
技术架构系列 - 详解Redis
数据结构·数据库·redis·分布式·缓存·中间件·架构
熊猫钓鱼>_>9 小时前
【开源鸿蒙跨平台开发先锋训练营】Day 19: 开源鸿蒙React Native动效体系构建与混合开发复盘
react native·华为·开源·harmonyos·鸿蒙·openharmony
9 小时前
java关于内部类
java·开发语言
好好沉淀9 小时前
Java 项目中的 .idea 与 target 文件夹
java·开发语言·intellij-idea
gusijin9 小时前
解决idea启动报错java: OutOfMemoryError: insufficient memory
java·ide·intellij-idea
To Be Clean Coder9 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
只是懒得想了9 小时前
C++实现密码破解工具:从MD5暴力破解到现代哈希安全实践
c++·算法·安全·哈希算法
吨~吨~吨~9 小时前
解决 IntelliJ IDEA 运行时“命令行过长”问题:使用 JAR
java·ide·intellij-idea