2025-09-06:字典序最小的生成字符串。用go语言,给定两个字符串 str1(长度为 n)和 str2(长度为 m)。我们要构造一个长度为 n + m − 1 的字符串 word,并且对每个下标 i(0 ≤ i ≤ n−1)都要满足一个约束:
-
当 str1[i] 为字符 ‘T’ 时,word 从位置 i 开始、长度为 m 的连续片段必须与 str2 完全相同;
-
当 str1[i] 为字符 ‘F’ 时,word 从位置 i 开始、长度为 m 的连续片段必须与 str2 不相同。
在满足上述所有约束的前提下,返回按字典序最小的那个 word;若没有任何字符串能满足这些条件,则返回空串 “”。字典序比较按常规字母表顺序进行:先比较第一个不同字符,字符更靠前的字符串更小;若一个字符串是另一个的前缀,则较短者更小。子串指字符串中连续的一段字符。
1 <= n == str1.length <= 10000。
1 <= m == str2.length <= 500。
str1 仅由 ‘T’ 或 ‘F’ 组成。
str2 仅由小写英文字母组成。
输入: str1 = “TFTF”, str2 = “ab”。
输出: “ababa”。
解释:
下表展示了字符串 “ababa” 的生成过程:
下标 | T/F | 长度为 m 的子字符串 |
---|---|---|
0 | ‘T’ | “ab” |
1 | ‘F’ | “ba” |
2 | ‘T’ | “ab” |
3 | ‘F’ | “ba” |
字符串 “ababa” 和 “ababb” 都可以由 str1 和 str2 生成。
返回 “ababa”,因为它的字典序更小。
题目来自力扣3474。
分步骤描述过程
1. 初始化结果字符串
- 创建一个长度为
n + m - 1
的字节数组ans
,初始时每个字符都是'?'
(表示待定位置)。
2. 处理 str1
中的 'T'
约束
- 遍历
str1
,对于每个'T'
的位置i
:- 找到前一个
'T'
出现的位置pre
(初始为-m
),计算重叠部分的大小size = max(pre + m - i, 0)
。这表示当前'T'
要求的子串与前一个'T'
要求的子串有size
个字符的重叠。 - 检查
str2
的长为size
的前缀和后缀是否相同(使用 Z 函数计算str2
的z
数组,检查z[m - size]
是否等于size
)。如果不相同,则无法满足约束,返回空串。 - 将
str2
从索引size
开始的部分复制到ans
中从i + size
开始的位置(即填充非重叠部分)。
- 找到前一个
- 这样,所有
'T'
约束要求的子串都被正确填充(重叠部分已验证,非重叠部分直接复制)。
3. 预处理待定位置('?'
)
- 创建一个数组
preQ
,记录每个位置之前(包括自身)最近的待定位置(即ans
中值为'?'
的位置)的索引。 - 同时,将所有待定位置初始化为
'a'
(字典序最小)。
4. 处理 str1
中的 'F'
约束
- 再次使用 Z 函数,计算
str2 + ans
的 Z 数组(用于快速判断任意位置开始的子串是否等于str2
)。 - 遍历
str1
,对于每个'F'
的位置i
:- 检查从
i
开始的子串是否等于str2
(通过 Z 数组:z[m + i] >= m
表示相等)。如果相等,则需要修改待定位置来破坏匹配。 - 找到子串
ans[i : i+m]
中最后一个待定位置j
(通过preQ[i+m-1]
获取)。如果没有待定位置(j < i
),则无法破坏匹配,返回空串。 - 将
ans[j]
改为'b'
(因为'a'
已经不能避免匹配,所以改为稍大的字符,但字典序仍尽量小),并跳过后续检查(直接让i = j
,避免重复修改同一区域)。
- 检查从
- 这样,每个
'F'
约束要求的子串都不等于str2
(通过修改最后一个待定位置为'b'
来破坏匹配)。
5. 返回结果
- 如果所有约束都满足,返回
ans
转换后的字符串;否则返回空串。
总的时间复杂度和额外空间复杂度
-
时间复杂度:
- 计算 Z 函数:每次计算的时间复杂度为 O(|字符串长度|)。这里计算了两次 Z 函数:一次用于
str2
(长度为m
),一次用于str2 + ans
(长度为m + (n+m-1) = n+2m-1
)。由于m <= 500
且n <= 10000
,所以总时间复杂度为 O(n + m)。 - 处理
'T'
约束:遍历str1
的'T'
位置,每次操作是常数时间(除了复制操作,但复制总长度不超过n+m
),所以为 O(n)。 - 预处理待定位置:遍历
ans
(长度为n+m-1
),O(n+m)。 - 处理
'F'
约束:遍历str1
的'F'
位置,每次检查 Z 数组是常数时间,修改待定位置也是常数时间,所以为 O(n)。 - 总体时间复杂度为 O(n + m)。
- 计算 Z 函数:每次计算的时间复杂度为 O(|字符串长度|)。这里计算了两次 Z 函数:一次用于
-
额外空间复杂度:
- Z 数组:最大为 O(n + m)(第二次计算 Z 函数时)。
- 结果字符串
ans
:长度为n+m-1
。 - 预处理数组
preQ
:长度为n+m-1
。 - 总体额外空间复杂度为 O(n + m)。
Go完整代码如下:
package main
import (
"bytes"
"fmt"
)
func calcZ(s string) []int {
n := len(s)
z := make([]int, n)
boxL, boxR := 0, 0 // z-box 左右边界(闭区间)
for i := 1; i < n; i++ {
if i <= boxR {
z[i] = min(z[i-boxL], boxR-i+1)
}
for i+z[i] < n && s[z[i]] == s[i+z[i]] {
boxL, boxR = i, i+z[i]
z[i]++
}
}
z[0] = n
return z
}
func generateString(s, t string) string {
n, m := len(s), len(t)
ans := bytes.Repeat([]byte{'?'}, n+m-1)
// 处理 T
pre := -m
z := calcZ(t)
for i, b := range s {
if b != 'T' {
continue
}
size := max(pre+m-i, 0)
// t 的长为 size 的前后缀必须相同
if size > 0 && z[m-size] < size {
return ""
}
// size 后的内容都是 '?',填入 t
copy(ans[i+size:], t[size:])
pre = i
}
// 计算 <= i 的最近待定位置
preQ := make([]int, len(ans))
pre = -1
for i, c := range ans {
if c == '?' {
ans[i] = 'a' // 待定位置的初始值为 a
pre = i
}
preQ[i] = pre
}
// 找 ans 中的等于 t 的位置,可以用 KMP 或者 Z 函数
z = calcZ(t + string(ans))
// 处理 F
for i := 0; i < n; i++ {
if s[i] != 'F' {
continue
}
// 子串必须不等于 t
if z[m+i] < m {
continue
}
// 找最后一个待定位置
j := preQ[i+m-1]
if j < i { // 没有
return ""
}
ans[j] = 'b'
i = j // 直接跳到 j
}
return string(ans)
}
func main() {
str1 := "TFTF"
str2 := "ab"
result := generateString(str1, str2)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def calc_z(s):
n = len(s)
z = [0] * n
box_l, box_r = 0, 0
for i in range(1, n):
if i <= box_r:
z[i] = min(z[i - box_l], box_r - i + 1)
while i + z[i] < n and s[z[i]] == s[i + z[i]]:
box_l, box_r = i, i + z[i]
z[i] += 1
z[0] = n
return z
def generate_string(s, t):
n, m = len(s), len(t)
ans = ['?'] * (n + m - 1)
# 处理 T
pre = -m
z_t = calc_z(t)
for i, char in enumerate(s):
if char != 'T':
continue
size = max(pre + m - i, 0)
if size > 0 and z_t[m - size] < size:
return ""
# 将 t[size:] 复制到 ans[i+size:]
for j in range(size, m):
if i + j < len(ans):
ans[i + j] = t[j]
pre = i
# 预处理最近待定位置
pre_q = [-1] * len(ans)
pre_val = -1
for i in range(len(ans)):
if ans[i] == '?':
ans[i] = 'a' # 初始化为 'a'
pre_val = i
pre_q[i] = pre_val
# 计算整个字符串的 Z 数组
concat_str = t + ''.join(ans)
z_total = calc_z(concat_str)
# 处理 F
i = 0
while i < n:
if s[i] != 'F':
i += 1
continue
# 检查从位置 i 开始的子串是否等于 t
if z_total[m + i] >= m:
# 需要修改最后一个待定位置
j = pre_q[i + m - 1]
if j < i:
return ""
ans[j] = 'b'
i = j # 跳到修改的位置
i += 1
return ''.join(ans)
def main():
str1 = "TFTF"
str2 = "ab"
result = generate_string(str1, str2)
print(result)
if __name__ == "__main__":
main()