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

2026-10-22:字符串变换后的最少分组数。用go语言,给定一个字符串集合。对其中任意一个字符串,先按字符所在位置分成两串:位置编号为 1

0
分享至

2026-10-22:字符串变换后的最少分组数。用go语言,给定一个字符串集合。对其中任意一个字符串,先按字符所在位置分成两串:位置编号为 1、3、5……的字符按原顺序组成一串,位置编号为 2、4、6……的字符按原顺序组成另一串。然后允许分别把这两串字符做任意次循环平移,平移次数可以是零;最后再按原来的位置交错放回,得到一个新字符串。

如果两个字符串能够通过上述方式变成同一个字符串,就认为它们属于同一种形态。现在要把所有给定字符串分成若干组,要求每个字符串只出现在一个组里,并且同一组内任意两个字符串都具有这种相同形态。问最少需要分成多少组。换句话说,就是统计这些字符串一共包含多少种不同的上述形态;长度不同的字符串不可能归为同一种形态。

1 <= words.length <= 100000

1 <= words[i].length <= 500000

所有 words[i].length 之和不超过 500000。

words[i] 仅由小写英文字母组成。

输入: words = ["ntgwz","zwntg"]。

输出: 1。

解释:

对于 "ntgwz",偶数下标字符组成的子序列为 "ngz",奇数下标字符组成的子序列为 "tw"。

将 "ngz" 向右循环移动 1 位,得到 "zng";将 "tw" 向右循环移动 1 位,得到 "wt"。

重新构造字符串后,得到 "zwntg"。

因此,这两个字符串等价,可以划分到同一组中。

题目来自力扣3999。

大体步骤如下:

一、把字符串拆成两个子序列

对每个字符串,按照字符下标的奇偶性拆成两个部分:

  1. 1. 收集偶数下标位置的字符。代码中下标从 0 开始,所以收集的是第 0、2、4……个字符;对应题目中位置编号为 1、3、5……的字符。

  2. 2. 收集奇数下标位置的字符。代码中收集的是第 1、3、5……个字符;对应题目中位置编号为 2、4、6……的字符。

这两个子序列都保持字符在原字符串中的相对顺序。拆分后,原字符串长度等于两个子序列长度之和。对于长度为 1 的字符串,第二个子序列为空串,这是允许的。

二、对两个子序列分别求“字典序最小的循环同构串”

题目允许分别对偶数位子序列和奇数位子序列做任意次向右循环移动。一个序列向右循环移动若干次,本质上就是把这个序列看成一个环,从某个位置开始重新读一圈。所有可能的移动结果,就是这个序列的所有循环旋转。

为了比较两个序列是否可以通过循环移动互相转化,不需要枚举所有移动次数,而是为每个序列选一个唯一的代表:在所有循环旋转中,字典序最小的那个串。这个代表称为“最小循环表示”或“最小表示”。两个序列循环同构,当且仅当它们的最小循环表示完全相同。

求一个长度为 m 的序列的最小表示时,代码使用双指针排除法:

  1. 1. 先把序列看成“原序列接上自身”得到的长度为 2m 的串,这样从任意起点读取连续 m 个字符,就对应原序列的一个循环旋转。

  2. 2. 维护两个候选起点,初始分别指向第 0 个位置和第 1 个位置。目标是在这些起点中找到从哪个起点开始读出的长度为 m 的串字典序最小。

  3. 3. 从两个候选起点开始逐字符比较。假设比较到第 k 个字符时第一次出现不同:

  • • 如果前一个候选起点对应的字符更小,那么从后一个候选起点到它后面连续若干位置开始的旋转,都不可能是最小表示,因为它们在第一次不同处已经输给了前一个候选对应的旋转。于是把后一个候选起点直接跳过这一整段,移到第一个不同位置之后。

  • • 如果后一个候选起点对应的字符更小,说明后一个候选更优。此时前一个候选起点以及它后面一段起点都不可能是答案,于是把前一个候选起点更新为后一个候选起点,同时把后一个候选起点跳到至少旧后一个候选起点的下一个位置,并且至少跳过刚刚比较过的那一段,取二者中较大的位置。

4. 如果在比较过程中连续比较了 m 个字符都没有不同,说明两个候选起点读出的循环串完全相同,或者序列具有周期性。此时当前保存的较优候选起点已经可以作为最小表示的起点,停止比较。

5. 循环直到第二个候选起点到达原序列长度 m。最终第一个候选起点指向的位置,就是字典序最小循环旋转的起点;从该位置连续取 m 个字符,就是该序列的最小表示。

这个双指针过程看似有两层循环,但每一轮比较后都会排除掉一段不可能成为最小起点的位置,累计排除次数与序列长度同阶,因此求一个长度为 m 的序列的最小表示是线性时间。

三、把两个最小表示拼接成规范签名

对当前字符串的偶数位子序列求出最小表示,对奇数位子序列也求出最小表示。然后把“偶数位子序列的最小表示”放在前面,“奇数位子序列的最小表示”放在后面,拼接成一个新的字符串,作为当前原字符串的规范签名。

这里顺序必须固定,不能交换,也不能把两个最小表示排序。原因是偶数位和奇数位在变换中角色不同,拼接顺序保留了这种角色信息。同时,拼接后的总长度等于原字符串长度,所以长度不同的字符串天然会得到不同长度的签名,不会被误判为同一类。

四、用哈希集合统计不同签名

遍历所有给定字符串,对每个字符串都生成上述规范签名,然后把签名放入一个哈希集合中。哈希集合会自动去重:同一个签名无论出现多少次,只保留一个。遍历结束后,集合中元素的数量就是不同等价类的数量。

为什么集合大小就是最少分组数?

  1. 1. 如果两个字符串等价,那么它们的偶数位子序列循环同构,奇数位子序列也循环同构。循环同构的序列具有相同的最小表示,因此两个字符串生成的签名完全相同,会落在同一个集合元素中。

  2. 2. 反过来,如果两个字符串生成的签名相同,说明偶数位子序列的最小表示相同,奇数位子序列的最小表示也相同,因此两个子序列分别循环同构。于是可以分别对两个子序列做适当的循环移动,使两个字符串变成同一个字符串,所以它们等价。

  3. 3. 等价关系具有传递性,每个字符串只对应一个签名,所以每个字符串恰好属于一个等价类。每个等价类至少需要一组,而把一个等价类整体放成一组又一定满足组内任意两个等价,因此最少分组数正好等于等价类数量,也就是哈希集合的大小。

对于示例中的两个字符串,它们虽然表面不同,但按奇偶下标拆分后,两个子序列分别属于相同的循环同构类,因此得到相同的规范签名,集合大小为 1,输出 1。

五、复杂度分析

设所有输入字符串的长度总和为 N,即 N 等于所有 words[i].length 之和;设字符串个数为 M。

时间复杂度:

  1. 1. 对每个字符串按奇偶下标拆分,耗时与该字符串长度成正比。所有字符串合计 O(N)。

  2. 2. 对每个字符串的两个子序列分别求最小表示。两个子序列长度之和等于原字符串长度,而最小表示算法是线性的,所以每个字符串耗时 O(其长度),所有字符串合计 O(N)。

  3. 3. 把两个最小表示拼接成签名,耗时 O(原字符串长度)。所有字符串合计 O(N)。

  4. 4. 把签名插入哈希集合。按通常哈希表的期望分析,计算字符串哈希和插入的期望代价与签名长度成正比,所有签名总长度不超过 N,因此总期望 O(N)。如果讨论哈希冲突的最坏理论情况,可能退化,但按常规算法分析使用期望 O(N)。

所以总时间复杂度为期望 O(N),其中 N 是所有字符串长度之和。题目给出 N 不超过 500000,因此该复杂度足够。

额外空间复杂度:

  1. 1. 哈希集合需要保存所有不同的规范签名。每个签名长度等于对应原字符串长度,去重后保存的签名总字符数不会超过所有输入字符串总长度 N,因此集合占用 O(N)。

  2. 2. 处理单个字符串时,需要临时保存拆出的两个子序列、双指针求最小表示时的临时串以及拼接签名等,单个字符串的临时空间与其长度成正比。所有字符串依次处理,临时空间可以复用,峰值不会超过 O(N)。

  3. 3. 因此除输入字符串本身外,总额外空间复杂度为 O(N)。

Go完整代码如下:

package main

import (
"fmt"
)

// 返回 s 的字典序最小的循环同构串
// 时间复杂度 O(|s|),证明见代码末尾的注释
func smallestRepresentation(s []byte) []byte {
n := len(s)
s = append(s, s...)
i := 0// 始终指向当前最小子串的首字母下标
j := 1// 指向需要和 i 比较的子串的首字母下标
for j < n {
// 暴力比较:是 i 开头的字典序小,还是 j 开头的字典序小?
k := 0
for k < n && s[i+k] == s[j+k] {
k++
}
if k >= n {
// s 是个周期字符串,周期为 j-i
// j+d 开头的子串等于 i+d 开头的子串,而这些子串我们之前已经排除了,继续遍历不会找到更小的
break
}

if s[i+k] < s[j+k] { // 注:如果求字典序最大,改成 >
// 比如从 i 开始是 "aaab",从 j 开始是 "aaac"
// 从 i 开始比从 j 开始更小(排除 j)
// 此外:
// 从 i+1 开始比从 j+1 开始更小,所以从 j+1 开始不可能是答案,排除
// 从 i+2 开始比从 j+2 开始更小,所以从 j+2 开始不可能是答案,排除
// ……
// 从 i+k 开始比从 j+k 开始更小,所以从 j+k 开始不可能是答案,排除
// 所以下一个「可能是答案」的开始位置是 j+k+1
j += k + 1
} else {
// 从 j 开始比从 i 开始更小,更新 i=j(也意味着我们排除了 i)
// 此外:
// 从 j+1 开始比从 i+1 开始更小,所以从 i+1 开始不可能是答案,排除
// 从 j+2 开始比从 i+2 开始更小,所以从 i+2 开始不可能是答案,排除
// ……
// 从 j+k 开始比从 i+k 开始更小,所以从 i+k 开始不可能是答案,排除
// 所以把 j 跳到 i+k+1,不过这可能比 j+1 小,所以与 j+1 取 max
// 综上所述,下一个「可能是答案」的开始位置是 max(j+1, i+k+1)
i, j = j, max(j, i+k)+1
}
// 每次要么排除 k+1 个与 i 相关的位置(这样的位置至多 n 个),要么排除 k+1 个与 j 相关的位置(这样的位置至多 n 个)
// 所以上面关于 k 的循环,∑k <= 2n,所以二重循环的总循环次数是 O(n) 的
}
return s[i : i+n]
}

func minimumGroups(words []string) (ans int) {
set := map[string]struct{}{}

for _, word := range words {
// 按照下标的奇偶性分组
groups := [2][]byte{}
for i, ch := range word {
groups[i%2] = append(groups[i%2], byte(ch))
}

// 分别计算偶数组和奇数组的最小表示
s := smallestRepresentation(groups[0])
t := smallestRepresentation(groups[1])

set[string(append(s, t...))] = struct{}{}
}

returnlen(set)
}

func main() {
words := []string{"ntgwz", "zwntg"}
result := minimumGroups(words)
fmt.Println(result)
}

Python完整代码如下:

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

def smallest_representation(s: str) -> str:
"""
返回字符串 s 的字典序最小的循环同构串。
时间复杂度 O(|s|)。
"""
n = len(s)
if n == 0:
return""

ss = s + s # 相当于 Go 里的 s = append(s, s...)
i = 0 # 始终指向当前最小子串的首字母下标
j = 1 # 指向需要和 i 比较的子串的首字母下标

while j < n:
k = 0
# 暴力比较:以 i 开头和以 j 开头的子串哪个字典序更小
while k < n and ss[i + k] == ss[j + k]:
k += 1

if k >= n:
# s 是周期字符串,周期为 j-i
# 继续遍历不会找到更小的
break

if ss[i + k] < ss[j + k]:
# 从 i 开始比从 j 开始更小,排除 j 到 j+k 这些起点
j += k + 1
else:
# 从 j 开始比从 i 开始更小,更新 i=j
# 并排除 i 到 i+k 这些起点
i, j = j, max(j, i + k) + 1

return ss[i:i + n]

def minimum_groups(words) -> int:
seen = set()

for word in words:
# 按照下标的奇偶性分组
groups = ["", ""]
for idx, ch in enumerate(word):
groups[idx % 2] += ch

# 分别计算偶数组和奇数组的最小表示
s = smallest_representation(groups[0])
t = smallest_representation(groups[1])

# 用「偶数组最小表示 + 奇数组最小表示」作为等价类标识
seen.add(s + t)

returnlen(seen)

def main():
words = ["ntgwz", "zwntg"]
result = minimum_groups(words)
print(result)

if __name__ == "__main__":
main()

C++完整代码如下:

  

using namespace std;

// 返回 s 的字典序最小的循环同构串
// 时间复杂度 O(|s|)
string smallestRepresentation(conststring& s) {
int n = (int)s.size();
if (n == 0) return"";

string ss = s + s; // 相当于 Go 里的 s = append(s, s...)
int i = 0; // 始终指向当前最小子串的首字母下标
int j = 1; // 指向需要和 i 比较的子串的首字母下标

while (j < n) {
// 暴力比较:是 i 开头的字典序小,还是 j 开头的字典序小?
int k = 0;
while (k < n && ss[i + k] == ss[j + k]) {
++k;
}

if (k >= n) {
// s 是个周期字符串,周期为 j-i
// j+d 开头的子串等于 i+d 开头的子串,而这些子串我们之前已经排除了
// 继续遍历不会找到更小的
break;
}

if (ss[i + k] < ss[j + k]) { // 注:如果求字典序最大,改成 >
// 比如从 i 开始是 "aaab",从 j 开始是 "aaac"
// 从 i 开始比从 j 开始更小(排除 j)
// 此外:
// 从 i+1 开始比从 j+1 开始更小,所以从 j+1 开始不可能是答案,排除
// 从 i+2 开始比从 j+2 开始更小,所以从 j+2 开始不可能是答案,排除
// ……
// 从 i+k 开始比从 j+k 开始更小,所以从 j+k 开始不可能是答案,排除
// 所以下一个「可能是答案」的开始位置是 j+k+1
j += k + 1;
} else {
// 从 j 开始比从 i 开始更小,更新 i=j(也意味着我们排除了 i)
// 此外:
// 从 j+1 开始比从 i+1 开始更小,所以从 i+1 开始不可能是答案,排除
// 从 j+2 开始比从 i+2 开始更小,所以从 i+2 开始不可能是答案,排除
// ……
// 从 j+k 开始比从 i+k 开始更小,所以从 i+k 开始不可能是答案,排除
// 所以把 j 跳到 i+k+1,不过这可能比 j+1 小,所以与 j+1 取 max
// 综上所述,下一个「可能是答案」的开始位置是 max(j+1, i+k+1)
int newJ = max(j, i + k) + 1;
i = j;
j = newJ;
}
// 每次要么排除 k+1 个与 i 相关的位置(这样的位置至多 n 个),要么排除 k+1 个与 j 相关的位置(这样的位置至多 n 个)
// 所以上面关于 k 的循环,∑k <= 2n,所以二重循环的总循环次数是 O(n) 的
}

return ss.substr(i, n);
}

int minimumGroups(const vector& words) {
set seen;

for (conststring& word : words) {
// 按照下标的奇偶性分组
string groups[2];
for (int idx = 0; idx < (int)word.size(); ++idx) {
groups[idx % 2].push_back(word[idx]);
}

// 分别计算偶数组和奇数组的最小表示
string s = smallestRepresentation(groups[0]);
string t = smallestRepresentation(groups[1]);

// 用「偶数组最小表示 + 奇数组最小表示」作为等价类标识
seen.insert(s + t);
}

return (int)seen.size();
}

int main() {
vector words = {"ntgwz", "zwntg"};
int result = minimumGroups(words);
cout << result << endl;
return0;
}

我们相信人工智能为普通人提供了一种“增强工具”,并致力于分享全方位的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.

相关推荐
热点推荐
日本网友让志田千阳“滚出日本、入籍中国”,结果她真的来了

日本网友让志田千阳“滚出日本、入籍中国”,结果她真的来了

叒女紫121
2026-10-15 17:59:37
驻日武官王庆简:潜伏在日本20年,出卖了无数军情,没料到最后竟被一个老习惯断送了性命

驻日武官王庆简:潜伏在日本20年,出卖了无数军情,没料到最后竟被一个老习惯断送了性命

磊子讲史
2026-09-16 18:16:40
日本部长访华参加APEC会议,站在中国领土上竟提要求放开稀土管制

日本部长访华参加APEC会议,站在中国领土上竟提要求放开稀土管制

今墨缘
2026-05-25 19:09:57
50岁赵薇面相变了,时隔多年仍想复出!坦言在家会写一些剧本!

50岁赵薇面相变了,时隔多年仍想复出!坦言在家会写一些剧本!

娱乐团长
2026-10-23 10:07:52
媒体人:拉塞尔这状态遇强悍防守真没办法,来都来了再看看

媒体人:拉塞尔这状态遇强悍防守真没办法,来都来了再看看

甜度百分百21
2026-10-23 00:59:23
32分钟轰45+7+6,正负值+22,骑士不该放走此人

32分钟轰45+7+6,正负值+22,骑士不该放走此人

篮球大视野
2026-10-22 17:54:59
埃迪豪成曼联下任主帅第一热门!孔蒂谈判受阻,拉爵不满其两要求

埃迪豪成曼联下任主帅第一热门!孔蒂谈判受阻,拉爵不满其两要求

罗米的曼联博客
2026-10-23 10:01:53
联合国机构:此次厄尔尼诺现象强度史无前例,超出了统计模型所能预测范围,强烈呼吁国际社会立即提供援助

联合国机构:此次厄尔尼诺现象强度史无前例,超出了统计模型所能预测范围,强烈呼吁国际社会立即提供援助

每日经济新闻
2026-10-22 16:25:59
孟羽童陪董明珠过72岁生日,“老板,现在才知道你说的那句热爱斗争的含金量”

孟羽童陪董明珠过72岁生日,“老板,现在才知道你说的那句热爱斗争的含金量”

都市快报橙柿互动
2026-10-22 23:00:59
无奈,河南男子入职2月被派东南亚,1万人没劝住,落地3天大变样

无奈,河南男子入职2月被派东南亚,1万人没劝住,落地3天大变样

流云随风去远方
2026-10-22 17:42:27
日媒:直发披肩的申裕斌太美了,与赛场“丸子头”判若两人

日媒:直发披肩的申裕斌太美了,与赛场“丸子头”判若两人

小椰的奶奶
2026-10-23 11:47:08
火箭客战马刺!预计首发出炉:3巨头配范乔丹,23岁前锋变主攻点

火箭客战马刺!预计首发出炉:3巨头配范乔丹,23岁前锋变主攻点

熊哥爱篮球
2026-10-23 12:46:03
批评特朗普?泽连斯基遭美总统特使“斥责”

批评特朗普?泽连斯基遭美总统特使“斥责”

新京报
2026-10-22 12:52:06
刚去查了张本宇的底,有些话憋了挺久,原来当年他全家改国籍,真不全是为了钱,更多是没路走了

刚去查了张本宇的底,有些话憋了挺久,原来当年他全家改国籍,真不全是为了钱,更多是没路走了

冰雨落花h
2026-10-15 20:29:12
被传上午被免职,下午被带走,登 24 年春晚却忽然消失的黄宏今咋样

被传上午被免职,下午被带走,登 24 年春晚却忽然消失的黄宏今咋样

星娱叨叨社
2026-10-08 14:52:34
深挖陈宇昕家境,不查不知道,背后真相让人倍感寒心

深挖陈宇昕家境,不查不知道,背后真相让人倍感寒心

阿废冷眼观察所
2026-10-20 11:20:02
杨瀚森战国王再获正名良机!卡尔森三人缺阵,小杨成内线唯一替补

杨瀚森战国王再获正名良机!卡尔森三人缺阵,小杨成内线唯一替补

萌妈
2026-10-23 11:58:37
被古代人的早熟程度震惊了,十几二十几岁他们就已经搅动天下风云

被古代人的早熟程度震惊了,十几二十几岁他们就已经搅动天下风云

夜深爱杂谈
2026-10-14 09:44:40
炸裂首秀!你好,浓眉哥!再次点燃NBA交易市场

炸裂首秀!你好,浓眉哥!再次点燃NBA交易市场

篮球实战宝典
2026-10-22 22:00:43
单局11-0惨败,张本智和输球后跪地扶桌,起身时教练队友已经走了!

单局11-0惨败,张本智和输球后跪地扶桌,起身时教练队友已经走了!

好乒乓
2026-10-23 01:52:58
2026-10-23 14:27:00
moonfdd incentive-icons
moonfdd
福大大架构师每日一题
1529文章数 83关注度
往期回顾 全部

科技要闻

抢运营商饭碗!马斯克又有大动作

头条要闻

两名中国嫌疑人被抓 涉嫌在迪拜绑架杀害三人埋尸沙漠

头条要闻

两名中国嫌疑人被抓 涉嫌在迪拜绑架杀害三人埋尸沙漠

体育要闻

没人看好的加图索,打出队史最佳开局

娱乐要闻

高梓淇向女友求婚,被女方父母接受

财经要闻

娃哈哈集团董事长许思敏离职

汽车要闻

泊车有绝学 新手亦高手 全新第四代博越L泊车篇

态度原创

教育
本地
数码
家居
军事航空

教育要闻

操场秒变“练兵场”!佳兴外国语萌娃披红花当“小兵”,种下报国少年梦

本地新闻

一场垂直登高赛跑出了"山城"的真正内核

数码要闻

入门卡溢价超60%!RTX 50系价格彻底崩坏:5050、5060 Ti仅差500元

家居要闻

2026建博会(广州) 公装联探展交流活动

军事要闻

美国或于明年开始扩建格陵兰岛军事设施

无障碍浏览 进入关怀版