最小循环子数组

一、题目描述

给定一个由若干整数组成的数组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;
  }
}
相关推荐
灵感__idea5 小时前
Hello 算法:“走一步看一步”的智慧
前端·javascript·算法
lwf0061647 小时前
导数学习日记
学习·算法·机器学习
yaoxin5211237 小时前
390. Java IO API - WatchDir 示例
java·前端·python
头发够用的程序员8 小时前
从滑动窗口到矩阵运算:img2col算法基本原理
人工智能·算法·yolo·性能优化·矩阵·边缘计算·jetson
武帝为此8 小时前
【数据清洗缺失值处理】
python·算法·数学建模
Halo_tjn9 小时前
Java 基于字符串相关知识点
java·开发语言·算法
梦想的颜色9 小时前
java 利用redis来限制用户频繁点击
java·开发语言
念越9 小时前
算法每日一题 Day08|双指针法解决三数之和
算法·力扣
黎阳之光9 小时前
黎阳之光透明管理:视频孪生重构智慧仓储新范式
人工智能·算法·安全·重构·数字孪生
PH = 710 小时前
OverlayFS联合文件系统使用示例
java·linux·服务器