
2025-12-09:分割字符串。用go语言,从索引 0 开始,对字符串按顺序切出一段子串。
逐字符向右扩展当前子串:如果它与之前已经产生过的任一子串相同,就继续加字符;一旦变成一个此前未出现过的新子串,就停止扩展。
把这个新子串加入结果集合并记为已出现,然后从下一个位置继续重复上述过程,直至处理完整个字符串。
最终返回一个数组 segments,按生成顺序记录所有得到的子串(segments[i] 表示第 i 个生成的片段)。
1 <= s.length <= 100000。
s 仅包含小写英文字母。
输入: s = "abbccccd"。
输出: ["a","b","bc","c","cc","d"]。
解释:
下标 | 添加后的段 | 已经出现过的段 | 当前段是否已经出现过? | 新段 | 更新后已经出现过的段 |
|---|---|---|---|---|---|
0 | "a" | [] | 否 | "" | ["a"] |
1 | "b" | ["a"] | 否 | "" | ["a", "b"] |
2 | "b" | ["a", "b"] | 是 | "b" | ["a", "b"] |
3 | "bc" | ["a", "b"] | 否 | "" | ["a", "b", "bc"] |
4 | "c" | ["a", "b", "bc"] | 否 | "" | ["a", "b", "bc", "c"] |
5 | "c" | ["a", "b", "bc", "c"] | 是 | "c" | ["a", "b", "bc", "c"] |
6 | "cc" | ["a", "b", "bc", "c"] | 否 | "" | ["a", "b", "bc", "c", "cc"] |
7 | "d" | ["a", "b", "bc", "c", "cc"] | 否 | "" | ["a", "b", "bc", "c", "cc", "d"] |
因此,最终输出为 ["a", "b", "bc", "c", "cc", "d"]。
题目来自力扣3597。
我们以输入字符串 s = "abbccccd" 为例,结合你提供的表格进行说明。算法维护两个关键数据结构:一个用于记录结果的字符串切片 segments,和一个用于记录已出现子串的集合 seen(在代码中是一个 map[string]bool)。
i 置为 0,指向当前子串的起始位置。segments 和 seen 均为空。i 未到达字符串末尾,就开启一轮新的子串分割。j 从 i 开始,逐步向右移动,尝试扩展当前子串 s[i:j+1]。s[i:j+1] 是否存在于 seen 集合中:j 继续向右移动一位,尝试添加一个字符,形成一个更长的子串,希望这个更长的子串是新的。s[i:j+1] 追加到结果切片 segments 的末尾。seen 集合中,标记为已出现。i 更新为 j + 1,从而开始下一轮的分割。结合例子 "abbccccd" 的关键过程解读:
"a" 开始。"a" 不在 seen 中,直接分割,得到 ["a"]。"b" 开始。"b" 不在 seen 中,直接分割,得到 ["a", "b"]。"b" 开始。单字符 "b" 已经在 seen 中,因此必须扩展为 "bc"。"bc" 是新的,所以分割,得到 ["a", "b", "bc"]。"c"出现时,因为之前"bc"中已包含"c"导致"c"已存在?这里需要仔细看:当算法处理到第四个字符'c'(下标3)时,seen里是["a", "b", "bc"],单字符"c"是新的,所以此时会分割出"c"。之后再遇到单个'c'时,因为它已存在,就需要扩展成"cc" 才成为新子串。这个过程确保了每个被记录的片段都是在其被创建的瞬间是独一无二的。
n 是字符串 s 的长度。seen 集合:在最坏情况下,算法可能会产生 O(n) 个不同的子串。然而,这些子串本身可能很长。例如,当字符串由同一个字符组成(如 "aaaa")时,可能会产生 "a", "aa", "aaa", "aaaa" 等子串。存储这些子串所需的总空间是 1 + 2 + 3 + ... + n = O(n²)。segments 切片:它存储的是对原字符串的引用或子串的拷贝(取决于Go语言的字符串实现)。在最坏情况下,它也需要存储 O(n) 个子串,总空间同样可能达到 O(n²)。
因此,空间复杂度主要由存储所有唯一子串的成本决定,为 O(n²)。j 每次从 i 开始向右移动,最坏情况下需要扫描 O(n) 个字符。s[i:j+1],都需要在 seen 集合(哈希表)中检查其是否存在。哈希表查找的平均时间复杂度是 O(k),其中 k 是键的长度。在这里,键就是子串 s[i:j+1],其长度最大为 O(n)。
将这三者相乘,最坏情况下的总时间复杂度是 O(n) * O(n) * O(n) = O(n³)。希望这个分步骤的详解和复杂度分析能帮助你完全理解这个算法!
.
package main
import (
"fmt"
)
func partitionString(s string) []string {
segments := []string{}
seen := make(map[string]bool)
i := 0
for i < len(s) {
j := i
// 寻找最小的未出现过的子串
for j < len(s) {
substr := s[i : j+1]
if seen[substr] {
j++
} else {
break
}
}
// 确保没有越界
if j >= len(s) {
j = len(s) - 1
}
substr := s[i : j+1]
segments = append(segments, substr)
seen[substr] = true
i = j + 1
}
return segments
}
func main() {
s := "abbccccd"
result := partitionString(s)
fmt.Println(result)
}

.
# -*-coding:utf-8-*-
from typing import List
def partition_string(s: str) -> List[str]:
segments = []
seen = set()
i = 0
while i < len(s):
# 寻找最小的未出现过的子串
j = i
while j < len(s) and s[i:j+1] in seen:
j += 1
# 获取子串
substr = s[i:j+1]
segments.append(substr)
seen.add(substr)
i = j + 1
return segments
# 测试
if __name__ == "__main__":
s = "abbccccd"
result = partition_string(s)
print(result) 
.
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
std::vector<std::string> partitionString(const std::string& s) {
std::vector<std::string> segments;
std::unordered_set<std::string> seen;
int i = 0;
int n = s.length();
while (i < n) {
int j = i;
// 寻找最小的未出现过的子串
while (j < n) {
std::string substr = s.substr(i, j - i + 1);
if (seen.find(substr) != seen.end()) {
j++;
} else {
break;
}
}
// 确保没有越界
if (j >= n) {
j = n - 1;
}
std::string substr = s.substr(i, j - i + 1);
segments.push_back(substr);
seen.insert(substr);
i = j + 1;
}
return segments;
}
int main() {
std::string s = "abbccccd";
std::vector<std::string> result = partitionString(s);
std::cout << "[";
for (size_t i = 0; i < result.size(); i++) {
std::cout << "\"" << result[i] << "\"";
if (i < result.size() - 1) {
std::cout << ", ";
}
}
std::cout << "]" << std::endl;
return0;
}

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