目录

3213:最小代价构造字符串(2170 分)

力扣第 405 场周赛第 4 题

题目

给你一个字符串 target、一个字符串数组 words 以及一个整数数组 costs,这两个数组长度相同。

设想一个空字符串 s

你可以执行以下操作任意次数(包括 次):

  • 选择一个在范围 [0, words.length - 1] 的索引 i
  • words[i] 追加到 s
  • 该操作的成本是 costs[i]

返回使 s 等于 target最小 成本。如果不可能,返回 -1

示例 1:

输入: 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"

示例 2:

输入: target = "aaaa", words = ["z","zz","zzz"], costs = [1,10,100]

输出: -1

解释:

无法使 s 等于 target,因此返回 -1。

提示:

  • 1 <= target.length <= 5 * 104
  • 1 <= words.length == costs.length <= 5 * 104
  • 1 <= words[i].length <= target.length
  • 所有 words[i].length 的总和小于或等于 5 * 104
  • targetwords[i] 仅由小写英文字母组成。
  • 1 <= costs[i] <= 104

相似问题:

分析

  • 要用后缀匹配单词,是典型的 AC 自动机问题

解答

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Node:
    __slots__ = 'son', 'fail', 'last', 'sz', 'w'

    def __init__(self):
        self.son = [None] * 26
        self.fail = None  # u 的 fail 指向 v,v 是树中 u 的最长后缀
        self.last = None  # u 的 last 指向 v,v 是树中 u 的最长后缀且是某个单词的结尾
        self.sz = 0       # 假如 u 是某个单词的结尾,sz 即是单词长度
        self.w = inf      # 假如 u 是某个单词的结尾,w 即是单词成本

class AC:
    def __init__(self):
        self.root = p = Node()
        dum = Node()              # 哑节点,方便后续 build
        dum.son, p.fail, p.last = [p]*26, dum, p

    def add(self,s,w):
        p = self.root
        for c in s: 
            c = ord(c) - ord('a')
            if not p.son[c]:
                p.son[c] = Node()
            p = p.son[c]
        p.sz = len(s)
        p.w = min(p.w,w)

    def build(self):
        Q = deque([self.root])
        while Q:
            u = Q.popleft()
            for c,son in enumerate(u.son):
                if son:
                    son.fail = u.fail.son[c]
                    son.last = son.fail if son.fail.sz else son.fail.last
                    Q.append(son)
                else: #  虚拟子节点,失配时直接跳到下一个匹配位置
                    u.son[c] = u.fail.son[c]

class Solution:
    def minimumCost(self, target: str, words: List[str], costs: List[int]) -> int:
        ac = AC()
        for s,w in zip(words,costs):
            ac.add(s,w)
        ac.build()
        n = len(target)
        f = [0]+[inf]*n
        p = root = ac.root
        for i in range(1,n+1):
            c = ord(target[i-1])-ord('a')
            p = p.son[c]
            if p.sz:  
                f[i] = min(f[i],f[i-p.sz]+p.w)
            u = p.last
            while u!=root:
                tmp = f[i-u.sz]+u.w
                if tmp<f[i]:
                    f[i] = tmp
                u = u.last
        return f[-1] if f[-1]<inf else -1

6650 ms

*附加

  • AC 自动机还可以用数组形式
  • py 用 [n] * 26 的数组比 [26] * n 的数组快很多,所以要绕一些
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class AC:
    def __init__(self,n):                     # 总长度 n-1 的字符串
        self.son = [[0]*n for _ in range(26)]
        self.fail = [0]*n
        self.last = [0]*n
        self.sz = [0]*n
        self.w = [inf]*n
        self.i = 0

    def add(self,s,w):
        p = 0
        for c in s:
            c = ord(c)-ord('a')
            if not self.son[c][p]:
                self.son[c][p] = self.i = self.i+1
            p = self.son[c][p]
        self.sz[p] = len(s)
        self.w[p] = min(self.w[p],w)

    def build(self):
        Q = deque(A[0] for A in self.son if A[0])
        while Q:
            u = Q.popleft()
            for A in self.son:
                if A[u]:
                    v = self.fail[A[u]] = A[self.fail[u]]
                    self.last[A[u]] = v if self.sz[v] else self.last[v]
                    Q.append(A[u])
                else:    # 虚拟子节点,失配时直接跳到下一个匹配位置
                    A[u] = A[self.fail[u]]


class Solution:
    def minimumCost(self, target: str, words: List[str], costs: List[int]) -> int:
        ac = AC(sum(len(s) for s in words)+1)
        for s,w in zip(words,costs):
            ac.add(s,w)
        ac.build()
        n = len(target)
        f = [0]+[inf]*n
        p = 0
        for i in range(1,n+1):
            c = ord(target[i-1])-ord('a')
            p = ac.son[c][p]
            if ac.sz[p]:  
                f[i] = min(f[i],f[i-ac.sz[p]]+ac.w[p])
            u = ac.last[p]
            while u:
                tmp = f[i-ac.sz[u]]+ac.w[u]
                if tmp<f[i]:
                    f[i] = tmp
                u = ac.last[u]
        return f[-1] if f[-1]<inf else -1

5657 ms