2025-02-26:最小代价构造字符串。用go语言,给定一个目标字符串 target、一个字符串数组 words 和一个整数数组 costs,这两个数组的长度相同。
想象一个空字符串 s,你可以执行以下操作任意次数(包括0次):
1.从 words 数组中选择一个索引 i,满足 0 ≤ i < words.length。
2.将 words[i] 添加到 s。
3.进行此操作的费用为 costs[i]。
你的任务是返回使得 s 等于 target 的最小花费。如果无法构造出 target,则返回 -1。
1 <= target.length <= 5 * 10000。
1 <= words.length == costs.length <= 5 * 10000。
1 <= words[i].length <= target.length。
所有 words[i].length 的总和小于或等于 5 * 10000。
target 和 words[i] 仅由小写英文字母组成。
1 <= costs[i] <= 10000。
输入: target = “abcdef”, words = [“abdef”,“abc”,“d”,“def”,“ef”], costs = [100,1,1,10,5]。
输出: 7。
解释:
选择索引 1 并以成本 1 将 “abc” 追加到 s,得到 s = “abc”。
选择索引 2 并以成本 1 将 “d” 追加到 s,得到 s = “abcd”。
选择索引 4 并以成本 5 将 “ef” 追加到 s,得到 s = “abcdef”。
答案2025-02-26:
chatgpt
题目来自leetcode3213。
1.创建一个 AC 自动机的结构体,其中包含节点的定义和相关方法。
2.创建一个 put 方法,用于将单词插入到 AC 自动机中,同时记录对应的费用。
3.创建一个 buildFail 方法,用于构建 AC 自动机的失配指针和后缀链接。
4.创建一个 minimumCost 函数,接收目标字符串 target、单词数组 words 和对应费用数组 costs。
5.在 minimumCost 函数中,建立 AC 自动机并对其进行构建。
6.初始化一个状态数组 f,用于记录到达每个位置的最小花费。
7.遍历目标字符串 target 中的每个字符,更新状态数组 f 中的值。
8.返回状态数组 f 最后一个位置的值作为最小花费,如果无法构造出 target,则返回 -1。
9.在 main 函数中给定目标字符串 target、单词数组 words 和费用数组 costs,调用 minimumCost 函数并输出结果。
总的时间复杂度取决于构建 AC 自动机和更新状态数组 f 的过程,构建 AC 自动机的时间复杂度为 O(∑len(words)),更新状态数组 f 的时间复杂度为 O(n),其中 n 为目标字符串 target 的长度。因此,总的时间复杂度为 O(∑len(words) + n)。
总的额外空间复杂度主要消耗在构建 AC 自动机时的存储空间和状态数组 f 的空间,构建 AC 自动机的额外空间复杂度为 O(∑len(words)),状态数组 f 的额外空间复杂度为 O(n)。因此,总的额外空间复杂度为 O(∑len(words) + n)。
package main
import (
"fmt"
"math"
)
type node struct {
son [26]*node
fail *node // 当 cur.son[i] 不能匹配 target 中的某个字符时,cur.fail.son[i] 即为下一个待匹配节点(等于 root 则表示没有匹配)
last *node // 后缀链接(suffix link),用来快速跳到一定是某个 words[k] 的最后一个字母的节点(等于 root 则表示没有)
len int
cost int
}
type acam struct {
root *node
}
func (ac *acam) put(s string, cost int) {
cur := ac.root
for _, b := range s {
b -= 'a'
if cur.son[b] == nil {
cur.son[b] = &node{cost: math.MaxInt}
}
cur = cur.son[b]
}
cur.len = len(s)
cur.cost = min(cur.cost, cost)
}
func (ac *acam) buildFail() {
ac.root.fail = ac.root
ac.root.last = ac.root
q := []*node{}
for i, son := range ac.root.son[:] {
if son == nil {
ac.root.son[i] = ac.root
} else {
son.fail = ac.root // 第一层的失配指针,都指向根节点 ∅
son.last = ac.root
q = append(q, son)
}
}
// BFS
for len(q) > 0 {
cur := q[0]
q = q[1:]
for i, son := range cur.son[:] {
if son == nil {
// 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个
// 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母)
cur.son[i] = cur.fail.son[i]
continue
}
son.fail = cur.fail.son[i] // 计算失配位置
if son.fail.len > 0 {
son.last = son.fail
} else {
// 沿着 last 往上走,可以直接跳到一定是某个 words[k] 的最后一个字母的节点(如果跳到 root 表示没有匹配)
son.last = son.fail.last
}
q = append(q, son)
}
}
}
func minimumCost(target string, words []string, costs []int) int {
ac := &acam{root: &node{}}
for i, w := range words {
ac.put(w, costs[i])
}
ac.buildFail()
n := len(target)
f := make([]int, n+1)
cur := ac.root
for i, b := range target {
cur = cur.son[b-'a'] // 如果没有匹配相当于移动到 fail 的 son[b-'a']
i++
f[i] = math.MaxInt / 2
if cur.len > 0 { // 匹配到了一个尽可能长的 words[k]
f[i] = min(f[i], f[i-cur.len]+cur.cost)
}
// 还可能匹配其余更短的 words[k],要在 last 链上找
for match := cur.last; match != ac.root; match = match.last {
f[i] = min(f[i], f[i-match.len]+match.cost)
}
}
if f[n] == math.MaxInt/2 {
return -1
}
return f[n]
}
func main() {
target := "abcdef"
words := []string{"abdef","abc","d","def","ef"}
costs := []int{100,1,1,10,5}
result := minimumCost(target,words,costs)
fmt.Println(result)
}