LeetCode 题解工作台

转换字符串的最小成本 II

给你两个下标从 0 开始的字符串 source 和 target ,它们的长度均为 n 并且由 小写 英文字母组成。 另给你两个下标从 0 开始的字符串数组 original 和 changed ,以及一个整数数组 cost ,其中 cost[i] 代表将字符串 original[i] 更改为字符串…

category

6

题型

code_blocks

6

代码语言

hub

3

相关题

当前训练重点

困难 · 状态·转移·动态规划

bolt

答案摘要

根据题目描述,我们可以将每个字符串看作一个节点,每对字符串的转换成本看作一条有向边。那么我们先初始化一个 $26 \times 26$ 的二维数组 ,其中 表示字符串 转换成字符串 的最小成本。初始时 $g[i][j] = \infty$,如果 $i = j$,那么 $g[i][j] = 0$。在这里,我们可以借助字典树存储 `original` 和 `changed` 中的字符串以及对应的…

Interview AiBox logo

Interview AiBox 实时 AI 助手,陪你讲清 状态·转移·动态规划 题型思路

试试 AiBox 面试助手arrow_forward
description

题目描述

给你两个下标从 0 开始的字符串 sourcetarget ,它们的长度均为 n 并且由 小写 英文字母组成。

另给你两个下标从 0 开始的字符串数组 originalchanged ,以及一个整数数组 cost ,其中 cost[i] 代表将字符串 original[i] 更改为字符串 changed[i] 的成本。

你从字符串 source 开始。在一次操作中,如果 存在 任意 下标 j 满足 cost[j] == z  、original[j] == x 以及 changed[j] == y ,你就可以选择字符串中的 子串 x 并以 z 的成本将其更改为 y 。 你可以执行 任意数量 的操作,但是任两次操作必须满足 以下两个 条件 之一

  • 在两次操作中选择的子串分别是 source[a..b]source[c..d] ,满足 b < c  d < a 。换句话说,两次操作中选择的下标 不相交
  • 在两次操作中选择的子串分别是 source[a..b]source[c..d] ,满足 a == c b == d 。换句话说,两次操作中选择的下标 相同

返回将字符串 source 转换为字符串 target 所需的 最小 成本。如果不可能完成转换,则返回 -1

注意,可能存在下标 ij 使得 original[j] == original[i]changed[j] == changed[i]

 

示例 1:

输入:source = "abcd", target = "acbe", original = ["a","b","c","c","e","d"], changed = ["b","c","b","e","b","e"], cost = [2,5,5,1,2,20]
输出:28
解释:将 "abcd" 转换为 "acbe",执行以下操作:
- 将子串 source[1..1] 从 "b" 改为 "c" ,成本为 5 。
- 将子串 source[2..2] 从 "c" 改为 "e" ,成本为 1 。
- 将子串 source[2..2] 从 "e" 改为 "b" ,成本为 2 。
- 将子串 source[3..3] 从 "d" 改为 "e" ,成本为 20 。
产生的总成本是 5 + 1 + 2 + 20 = 28 。 
可以证明这是可能的最小成本。

示例 2:

输入:source = "abcdefgh", target = "acdeeghh", original = ["bcd","fgh","thh"], changed = ["cde","thh","ghh"], cost = [1,3,5]
输出:9
解释:将 "abcdefgh" 转换为 "acdeeghh",执行以下操作:
- 将子串 source[1..3] 从 "bcd" 改为 "cde" ,成本为 1 。
- 将子串 source[5..7] 从 "fgh" 改为 "thh" ,成本为 3 。可以执行此操作,因为下标 [5,7] 与第一次操作选中的下标不相交。
- 将子串 source[5..7] 从 "thh" 改为 "ghh" ,成本为 5 。可以执行此操作,因为下标 [5,7] 与第一次操作选中的下标不相交,且与第二次操作选中的下标相同。
产生的总成本是 1 + 3 + 5 = 9 。
可以证明这是可能的最小成本。

示例 3:

输入:source = "abcdefgh", target = "addddddd", original = ["bcd","defgh"], changed = ["ddd","ddddd"], cost = [100,1578]
输出:-1
解释:无法将 "abcdefgh" 转换为 "addddddd" 。
如果选择子串 source[1..3] 执行第一次操作,以将 "abcdefgh" 改为 "adddefgh" ,你无法选择子串 source[3..7] 执行第二次操作,因为两次操作有一个共用下标 3 。
如果选择子串 source[3..7] 执行第一次操作,以将 "abcdefgh" 改为 "abcddddd" ,你无法选择子串 source[1..3] 执行第二次操作,因为两次操作有一个共用下标 3 。

 

提示:

  • 1 <= source.length == target.length <= 1000
  • sourcetarget 均由小写英文字母组成
  • 1 <= cost.length == original.length == changed.length <= 100
  • 1 <= original[i].length == changed[i].length <= source.length
  • original[i]changed[i] 均由小写英文字母组成
  • original[i] != changed[i]
  • 1 <= cost[i] <= 106
lightbulb

解题思路

方法一:字典树 + Floyd 算法 + 记忆化搜索

根据题目描述,我们可以将每个字符串看作一个节点,每对字符串的转换成本看作一条有向边。那么我们先初始化一个 26×2626 \times 26 的二维数组 gg,其中 g[i][j]g[i][j] 表示字符串 ii 转换成字符串 jj 的最小成本。初始时 g[i][j]=g[i][j] = \infty,如果 i=ji = j,那么 g[i][j]=0g[i][j] = 0。在这里,我们可以借助字典树存储 originalchanged 中的字符串以及对应的整数编号。

然后,我们使用 Floyd 算法计算出任意两个字符串之间的最小成本。

接下来,我们定义函数 dfs(i)dfs(i) 表示将字符串 source[i..]source[i..] 转换为字符串 target[i..]target[i..] 所需的最小成本。那么答案就是 dfs(0)dfs(0)

函数 dfs(i)dfs(i) 的计算过程如下:

  • 如果 isourcei \geq |source|,说明不需要转换,返回 00
  • 否则,如果 source[i]=target[i]source[i] = target[i],那么可以直接跳过,我们直接递归计算 dfs(i+1)dfs(i + 1)。我们也可以在 [i,source)[i, |source|) 的范围内枚举下标 jj,如果 source[i..j]source[i..j]target[i..j]target[i..j] 都在字典树中,且其对应的整数编号 xxyy 都大于等于 00,那么我们可以将 dfs(j+1)dfs(j + 1)g[x][y]g[x][y] 相加,得到一种转换方案的成本,我们取所有方案中的最小值。

综上,我们可以得到:

dfs(i)={0,isourcedfs(i+1),source[i]=target[i]minij<source{dfs(j+1)+g[x][y]},otherwisedfs(i) = \begin{cases} 0, & i \geq |source| \\ dfs(i + 1), & source[i] = target[i] \\ \min_{i \leq j < |source|} \{ dfs(j + 1) + g[x][y] \}, & \textit{otherwise} \end{cases}

其中 xxyy 分别是 source[i..j]source[i..j]target[i..j]target[i..j] 在字典树中对应的整数编号。

为了避免重复计算,我们可以使用记忆化搜索。

时间复杂度 O(m3+n2+m×n)O(m^3 + n^2 + m \times n),空间复杂度 O(m2+m×n+n)O(m^2 + m \times n + n)。其中 mmnn 分别是数组 originaloriginalsourcesource 的长度。

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
60
61
62
63
64
65
66
67
68
69
class Node:
    __slots__ = ["children", "v"]

    def __init__(self):
        self.children: List[Node | None] = [None] * 26
        self.v = -1


class Solution:
    def minimumCost(
        self,
        source: str,
        target: str,
        original: List[str],
        changed: List[str],
        cost: List[int],
    ) -> int:
        m = len(cost)
        g = [[inf] * (m << 1) for _ in range(m << 1)]
        for i in range(m << 1):
            g[i][i] = 0
        root = Node()
        idx = 0

        def insert(w: str) -> int:
            node = root
            for c in w:
                i = ord(c) - ord("a")
                if node.children[i] is None:
                    node.children[i] = Node()
                node = node.children[i]
            if node.v < 0:
                nonlocal idx
                node.v = idx
                idx += 1
            return node.v

        @cache
        def dfs(i: int) -> int:
            if i >= len(source):
                return 0
            res = dfs(i + 1) if source[i] == target[i] else inf
            p = q = root
            for j in range(i, len(source)):
                p = p.children[ord(source[j]) - ord("a")]
                q = q.children[ord(target[j]) - ord("a")]
                if p is None or q is None:
                    break
                if p.v < 0 or q.v < 0:
                    continue
                res = min(res, dfs(j + 1) + g[p.v][q.v])
            return res

        for x, y, z in zip(original, changed, cost):
            x = insert(x)
            y = insert(y)
            g[x][y] = min(g[x][y], z)
        for k in range(idx):
            for i in range(idx):
                if g[i][k] >= inf:
                    continue
                for j in range(idx):
                    # g[i][j] = min(g[i][j], g[i][k] + g[k][j])
                    if g[i][k] + g[k][j] < g[i][j]:
                        g[i][j] = g[i][k] + g[k][j]

        ans = dfs(0)
        return -1 if ans >= inf else ans
speed

复杂度分析

指标
时间and space complexity depend on the number of substring operations and the length of source. Mapping strings to IDs and iterating over operations leads to O(n * m) worst-case, with n = source length and m = number of operations. Space is dominated by dp array and substring mapping.
空间Depends on the final approach
psychology

面试官常问的追问

外企场景
  • question_mark

    Check if candidate correctly handles non-overlapping or identical index rules.

  • question_mark

    Observe if state transition DP is applied efficiently with substring mapping.

  • question_mark

    Listen for reasoning about impossible conversions and early pruning strategies.

warning

常见陷阱

外企场景
  • error

    Forgetting to enforce non-overlapping or identical index constraint between operations.

  • error

    Failing to efficiently match substrings, leading to TLE on larger inputs.

  • error

    Ignoring edge cases where conversion is impossible, returning wrong positive cost.

swap_horiz

进阶变体

外企场景
  • arrow_right_alt

    Allow wildcard characters in original substrings to match multiple target sequences.

  • arrow_right_alt

    Minimize number of operations instead of total cost while using the same DP framework.

  • arrow_right_alt

    Restrict operations to contiguous blocks of fixed maximum length to test optimized DP transitions.

help

常见问题

外企场景

转换字符串的最小成本 II题解:状态·转移·动态规划 | LeetCode #2977 困难