网易首页 > 网易号 > 正文 申请入驻

2026-08-27:最短唯一子数组。用go语言,给定一个整数数组,我们需要找到所有可能的连续非空片段中,那些在数组里只出现一次的片段。所谓

0
分享至

2026-08-27:最短唯一子数组。用go语言,给定一个整数数组,我们需要找到所有可能的连续非空片段中,那些在数组里只出现一次的片段。所谓“出现一次”,是指不存在另一个片段,长度相同且每个对应位置的数字都完全一样。我们的目标是找出所有这样的独特片段里,长度最短的那个,并返回这个最小长度值。

1 <= nums.length <= 100000。

1 <= nums[i] <= 100000。

输入: nums = [3,3,3]。

输出: 3。

解释:

长度为 1 的子数组:[3] → 出现 3 次

长度为 2 的子数组:[3, 3] → 出现 2 次

长度为 3 的子数组:[3, 3, 3] → 出现 1 次

子数组 [3, 3, 3] 是唯一的,因此最小唯一子数组的长度为 3。

题目来自力扣3934。

算法分步骤描述(基于后缀数组 + LCP) 问题核心

给定整数数组nums,需要找出所有仅出现一次的连续子数组(即不存在另一个完全相同的子数组),并返回其中最短长度
等价于:对每个后缀nums[i:],其所有前缀中,若某个前缀在其他后缀中不再出现,则它是一个唯一子数组;我们要找所有后缀中符合条件的最短前缀长度

步骤 1:将整数数组转化为字节序列(用于后缀数组构造)

  • • 题目中nums[i] <= 1e5,每个整数可以用 3 个字节完整表示(位移操作)。

  • • 将每个整数拆成 3 个字节(高位到低位),拼接成一个大的字节数组tmp

  • • 这样做的目的是借用 Go 标准库suffixarray直接处理字节切片,避免手动实现整数后缀数组。
    (注:由于每个整数固定 3 字节,整数数组的后缀与字节序列中偏移为 3 的倍数的后缀一一对应。)

步骤 2:构造后缀数组并转换为整数下标
  • • 调用suffixarray.New(tmp)得到后缀数组(内部为sa,类型[]int32),它记录了字节序列中所有后缀的字典序排名。

  • • 由于整数后缀只对应偏移为3 的倍数的起始位置,我们遍历sa,只保留p % 3 == 0的位置,并将坐标除以 3 得到原整数数组的下标。

  • • 最终得到整数数组nums的后缀数组sa(长度 n),sa[i]表示字典序第 i 小的后缀在原数组中的起始索引(0-based)。

步骤 3:建立排名数组rank
  • rank[p]表示后缀nums[p:]在字典序中的排名(即sa[rank[p]] == p)。

  • • 遍历sa,对每个索引 i,令rank[ sa[i] ] = i

步骤 4:计算高度数组height(LCP 数组)
  • height[0] = 0(哨兵)。

  • • 对于 i > 0,height[i]= 后缀nums[ sa[i] : ]nums[ sa[i-1] : ]的最长公共前缀长度。

  • • 利用Kasai 算法线性计算:

    • • 从 i = 0 到 n-1,令 h = 当前已经匹配的长度(初始 0)。

    • • 若rank[i] > 0,则与排名前一位的后缀比较,不断扩展公共前缀长度 h(同时保证不越界)。

    • • 记录height[ rank[i] ] = h,然后若 h>0,则 h--(因为下一次 i+1 时,前缀长度至少为 h-1)。

步骤 5:求每个后缀可形成的最短唯一子数组长度
  • • 对于后缀nums[ sa[i] : ],它与左右相邻后缀(即排名 i-1 和 i+1)的 LCP 最大值maxLCP决定了:
    任何长度 ≤maxLCP的前缀都会在相邻后缀中出现,因此不唯一
    长度 ≥maxLCP + 1的前缀才可能唯一。

  • • 因此,该后缀能贡献的最短唯一子数组长度为:

    • • 如果 i 不是最后一个(即 i < n-1),则考虑左右两边:uniqueLen = max(height[i], height[i+1]) + 1

    • • 如果 i 是最后一个(i == n-1),则只有左边:uniqueLen = height[i] + 1

  • • 同时,uniqueLen不能超过该后缀自身的长度(即n - sa[i]),否则子数组超出数组范围,不合理。

  • • 取所有合法uniqueLen的最小值,即为答案。

步骤 6:返回结果
  • • 初始ans = n(最大可能长度)。

  • • 遍历所有后缀,更新ans = min(ans, uniqueLen)

  • • 最终返回ans

示例推演(nums = [3,3,3])
  • • 后缀数组:所有后缀为[3,3,3],[3,3],[3],字典序相同(因为元素全等),排序后可能为[0,1,2][2,1,0],但实际顺序任意(只要排名稳定)。

  • • rank 数组:每个后缀排名相邻。

  • • height 数组:任意相邻后缀的 LCP 分别为 2 和 1(取决于排序),但最大值计算后可得:

    • • 对后缀[3,3,3],与左右 LCP 最大值 = 2,则 uniqueLen = 3,合法。

    • • 其他后缀的 uniqueLen 也会是 3(因为长度限制),最终 ans = 3。

时间与空间复杂度
  • 时间复杂度

    • • 构造后缀数组:suffixarray.New内部实现基于DC3 算法(线性),但理论上通常视为O(n),不过标准库可能采用快速排序(O(n log n))。严格来说,对于长度 n ≤ 1e5,可认为是O(n log n)

    • • 构建 rank 和 height:均 O(n)。

    • • 遍历求答案:O(n)。

    • • 总体O(n log n),且常数较小。

  • 额外空间复杂度

    • • 字节数组 tmp:O(n)。

    • • 后缀数组 sa:O(n)。

    • • rank 和 height 数组:O(n)。

    • • 其他辅助变量 O(1)。

    • • 总共O(n)

总结

该算法利用后缀数组 + LCP 快速判断前缀重复性,将“唯一子数组”问题转化为每个后缀的最短唯一前缀问题,从而在线性扫描中得到答案。空间开销为 O(n),时间开销为 O(n log n),能够处理 n = 1e5 的数据规模。

Go完整代码如下:

package main

import (
"fmt"
"index/suffixarray"
"unsafe"
)

func max(a, b int) int {
if a > b {
return a
}
return b
}

func min(a, b int) int {
if a < b {
return a
}
return b
}

func smallestUniqueSubarray(nums []int) int {
n := len(nums)
// 将每个整数拆成 3 个字节,用于构造后缀数组
tmp := make([]byte, 0, n*3)
for _, x := range nums {
tmp = append(tmp, byte(x>>16), byte(x>>8), byte(x))
}

// 利用 unsafe 获取 suffixarray 内部的 sa 切片
type _tp struct {
_ []byte
sa []int32
}
_sa := (*_tp)(unsafe.Pointer(suffixarray.New(tmp))).sa

// 只保留偏移为 3 的倍数的位置,对应原数组的整数后缀
sa := make([]int32, 0, n)
for _, p := range _sa {
if p%3 == 0 {
sa = append(sa, p/3)
}
}

// 后缀名次数组 rank
rank := make([]int, n)
for i, p := range sa {
rank[p] = i
}

// 高度数组 height(LCP 数组)
height := make([]int, n)
h := 0
for i, rk := range rank {
if h > 0 {
h--
}
if rk > 0 {
for j := int(sa[rk-1]); i+h < n && j+h < n && nums[i+h] == nums[j+h]; h++ {
}
}
height[rk] = h
}

ans := n
for i, h := range height {
// 该后缀与左右相邻后缀的 LCP 最大值 +1 即为最小唯一前缀长度
uniqueLength := h + 1
if i < n-1 {
uniqueLength = max(h, height[i+1]) + 1
}
if uniqueLength <= n-int(sa[i]) {
ans = min(ans, uniqueLength)
}
}
return ans
}

func main() {
nums := []int{3, 3, 3}
result := smallestUniqueSubarray(nums)
fmt.Println(result)
}

Python完整代码如下:

# -*-coding:utf-8-*-


def build_suffix_array(nums):
"""构建整数数组的后缀数组(倍增算法)"""
n = len(nums)
if n == 1:
return [0]
# 初始排名:直接用数值(但需注意数值可能较大,排序时依然正确)
rank = list(nums)
sa = list(range(n))
k = 1
tmp = [0] * n
while True:
# 按 (rank[i], rank[i+k] if i+k else -1 ) 排序
sa.sort(key=lambda i: (rank[i], rank[i + k] if i + k < n else -1 ))
tmp[sa[ 0 ]] = 0
for i in range ( 1 , n):
prev, cur = sa[i - 1 ], sa[i]
prev_key = (rank[prev], rank[prev + k] if prev + k < n else -1 )
cur_key = (rank[cur], rank[cur + k] if cur + k < n else -1 )
tmp[cur] = tmp[prev] + ( 1 if cur_key != prev_key else 0 )
rank, tmp = tmp, rank # 交换,tmp 变为旧 rank(后续会被覆盖)
if rank[sa[ -1 ]] == n - 1 : # 所有排名都不同
break
k <<= 1
return sa


def build_lcp(nums, sa):
"" "计算 LCP 数组(height),height[i] = LCP(sa[i], sa[i-1]),height[0]=0" ""
n = len (nums)
rank = [ 0 ] * n
for i, p in enumerate(sa):
rank[p] = i
height = [ 0 ] * n
h = 0
for i in range (n):
if rank[i] > 0 :
j = sa[rank[i] - 1 ]
while i + h < n and j + h < n and nums[i + h] == nums[j + h]:
h += 1
height[rank[i]] = h
if h > 0 :
h -= 1
return height


def smallest_unique_subarray(nums):
n = len (nums)
if n == 0 :
return 0

sa = build_suffix_array(nums)
height = build_lcp(nums, sa)

ans = n
for i in range (n):
# 当前后缀与左右相邻后缀的 LCP 最大值 + 1 即为最小唯一前缀长度
unique_len = height[i] + 1
if i < n - 1 :
unique_len = max(height[i], height[i + 1 ]) + 1
# 不能超过后缀自身的长度
if unique_len <= n - sa[i]:
ans = min(ans, unique_len)
return ans


if __name__ == "__main__" :
nums = [ 3 , 3 , 3 ]
result = smallest_unique_subarray(nums)
print (result)

C++完整代码如下:

  


using namespace std;

// 构建后缀数组 sa,sa[i] 表示第 i 小的后缀的起始下标
vector buildSuffixArray(const vector& nums) {
int n = nums.size();
vector sa(n), rank(n), tmp(n);
// 初始排名:按第一个元素
for (int i = 0; i < n; i++) {
sa[i] = i;
rank[i] = nums[i];
}
// 倍增排序
for (int k = 1; k < n; k <<= 1) {
auto cmp = [&](int i, int j) {
if (rank[i] != rank[j]) return rank[i] < rank[j];
int ri = (i + k < n) ? rank[i + k] : -1;
int rj = (j + k < n) ? rank[j + k] : -1;
return ri < rj;
};
sort(sa.begin(), sa.end(), cmp);
tmp[sa[0]] = 0;
for (int i = 1; i < n; i++) {
tmp[sa[i]] = tmp[sa[i - 1]] + (cmp(sa[i - 1], sa[i]) ? 1 : 0);
}
rank = tmp;
if (rank[sa[n - 1]] == n - 1) break; // 全部排名不同,提前结束
}
return sa;
}

// 计算 height 数组,height[i] = LCP(sa[i], sa[i-1]),height[0] = 0
vector buildHeight(const vector& nums, const vector& sa) {
int n = nums.size();
vector rank(n);
for (int i = 0; i < n; i++) rank[sa[i]] = i;
vector height(n, 0);
int h = 0;
for (int i = 0; i < n; i++) {
if (rank[i] > 0) {
int j = sa[rank[i] - 1];
while (i + h < n && j + h < n && nums[i + h] == nums[j + h]) h++;
height[rank[i]] = h;
if (h > 0) h--;
}
}
return height;
}

int smallestUniqueSubarray(const vector& nums) {
int n = nums.size();
if (n == 0) return 0; // 根据题意不会出现

vector sa = buildSuffixArray(nums);
vector height = buildHeight(nums, sa);

int ans = n;
for (int i = 0; i < n; i++) {
// 当前后缀与左右相邻后缀的 LCP 最大值 +1 即为最小唯一前缀长度
int uniqueLen = height[i] + 1;
if (i < n - 1) {
uniqueLen = max(height[i], height[i + 1]) + 1;
}
// 不能超过后缀自身长度
if (uniqueLen <= n - sa[i]) {
ans = min(ans, uniqueLen);
}
}
return ans;
}

int main() {
vector nums = {3, 3, 3};
int result = smallestUniqueSubarray(nums);
cout << result << endl;
return 0;
}

我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的AI知识。在这里,您可以找到最新的AI科普文章、工具评测、提升效率的秘籍以及行业洞察。 欢迎关注“福大大架构师每日一题”,发消息可获得面试资料,让AI助力您的未来发展。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
摸一下胸,拘留5天,图啥?

摸一下胸,拘留5天,图啥?

张晓磊
2026-08-22 11:37:55
俄为什么要把攻占乌克兰的4个州,写进宪法?

俄为什么要把攻占乌克兰的4个州,写进宪法?

律法刑道
2026-08-20 11:01:55
和中国纠葛千年的邻国:计划吞并中国占领次大陆,被中国一月击溃

和中国纠葛千年的邻国:计划吞并中国占领次大陆,被中国一月击溃

南书房
2026-08-27 13:00:10
我在养老院干了18年,劝大伙一句:儿女再孝顺,不如守好这6样

我在养老院干了18年,劝大伙一句:儿女再孝顺,不如守好这6样

小虎新车推荐员
2026-08-25 01:26:23
又一只大牛股杀疯了!601899业绩爆发,连续大涨!

又一只大牛股杀疯了!601899业绩爆发,连续大涨!

星图金融研究院
2026-08-28 07:49:55
亚洲又一艘国产航母来了!还没有下过水,就被外界吹成全球第一?

亚洲又一艘国产航母来了!还没有下过水,就被外界吹成全球第一?

涵豆说娱
2026-08-27 03:29:44
发出"灭国"警告,金正恩做出一个重大表态!

发出"灭国"警告,金正恩做出一个重大表态!

扶苏聊历史
2026-08-24 15:03:57
蒙古国政府再次公开拒绝了中方提出的跨境铁路运力扩容方案

蒙古国政府再次公开拒绝了中方提出的跨境铁路运力扩容方案

果妈聊娱乐
2026-08-23 08:38:03
十年前就在研制,十年后仍未露面,轰20设计方案极可能经过了大改

十年前就在研制,十年后仍未露面,轰20设计方案极可能经过了大改

军武吐槽君
2026-08-26 16:09:35
四川隆昌市发生5.1级地震 成都重庆等地有震感

四川隆昌市发生5.1级地震 成都重庆等地有震感

中国能源网
2026-08-28 14:51:02
1988年政治部主任深夜接军区命令:控制你们师长,他身上有三支枪

1988年政治部主任深夜接军区命令:控制你们师长,他身上有三支枪

云霄纪史观
2026-08-27 01:53:28
孙道临41岁那年总算娶到了王文娟,到了晚年他却崩溃大哭停不下来,他的背后藏着一段没人知晓的真实经历

孙道临41岁那年总算娶到了王文娟,到了晚年他却崩溃大哭停不下来,他的背后藏着一段没人知晓的真实经历

唠叨说历史
2026-08-20 16:23:02
萨拉赫吞下0-5惨败!土超豪门被罚下2人 48岁主帅当场辞职:太羞愧

萨拉赫吞下0-5惨败!土超豪门被罚下2人 48岁主帅当场辞职:太羞愧

风过乡
2026-08-28 07:13:46
7年前,华人女兵郑浩儿在美舰上,用中文警告中国海军,后来怎么了

7年前,华人女兵郑浩儿在美舰上,用中文警告中国海军,后来怎么了

西楼知趣杂谈
2026-08-27 11:16:33
张家辉曾公开爆料,杨紫进组后竟多次跟他“放话”:“张大哥,你知道吗?我可是内地最红的一线女明星!

张家辉曾公开爆料,杨紫进组后竟多次跟他“放话”:“张大哥,你知道吗?我可是内地最红的一线女明星!

LULU生活家
2026-08-24 19:42:50
千亿PCB龙头董事长突将近半数股份转给配偶,曾陷“电梯亲密视频”风波

千亿PCB龙头董事长突将近半数股份转给配偶,曾陷“电梯亲密视频”风波

红星新闻
2026-08-28 12:48:12
汪峰把公司1100人砍到400人,700人被裁。他说:“引入AI两个月,剩下400人干得比原来还好”,财务笑开了花。

汪峰把公司1100人砍到400人,700人被裁。他说:“引入AI两个月,剩下400人干得比原来还好”,财务笑开了花。

LULU生活家
2026-08-15 14:17:30
3亿镑!曼城疯狂大清洗送走4.75亿豪阵,10年王朝一夜推倒重建!

3亿镑!曼城疯狂大清洗送走4.75亿豪阵,10年王朝一夜推倒重建!

小桥流水q
2026-08-27 10:17:02
台名嘴沈富雄爆惊世骇俗言论:最终与大陆谈判统一大概率是民进党

台名嘴沈富雄爆惊世骇俗言论:最终与大陆谈判统一大概率是民进党

缘史记
2026-08-11 15:29:38
江青对他视如己出,胜过亲生女儿李讷,他为何会被判入狱17年?

江青对他视如己出,胜过亲生女儿李讷,他为何会被判入狱17年?

扬平说史
2026-08-16 22:27:42
2026-08-28 16:28:51
moonfdd incentive-icons
moonfdd
福大大架构师每日一题
1420文章数 80关注度
往期回顾 全部

科技要闻

AI牛马永不下班!OpenAI让智能体自己找活

头条要闻

孙宇晨承认长文有虚构 其曾靠写作从三本逆袭到北大

头条要闻

孙宇晨承认长文有虚构 其曾靠写作从三本逆袭到北大

体育要闻

曼城花1个亿,买下了摩洛哥的“国民女婿”

娱乐要闻

孙宇晨魔性长文加真实诉讼,围剿景甜

财经要闻

邢自强:经济破局关键在于夯实社会保障

汽车要闻

享界G9现身机器人运动会 国产豪华智驾对话未来

态度原创

本地
房产
时尚
健康
军事航空

本地新闻

昆明的雨,解锁汪曾祺的浪漫雨季

房产要闻

突发,三亚又大量卖地!

带火今年最热门的鞋,王菲合影秒变小迷妹,从不跟风的她为什么是爆款制造机?

突发脑梗怎么救命?一定看这两条路

军事要闻

"中华第一舰"退出现役

无障碍浏览 进入关怀版