LeetCode 题解工作台

形成目标字符串需要的最少字符串数 II

给你一个字符串数组 words 和一个字符串 target 。 如果字符串 x 是 words 中 任意 字符串的 前缀 ,则认为 x 是一个 有效 字符串。 现计划通过 连接 有效字符串形成 target ,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target ,则返…

category

8

题型

code_blocks

6

代码语言

hub

3

相关题

当前训练重点

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

bolt

答案摘要

由于本题数据规模较大,使用“字典树 + 记忆化搜索”的方法将会超时,我们需要寻找一种更高效的解法。 考虑从字符串 的第 个字符开始,最远能够匹配的字符串长度,假设为 ,那么对于任意 $j \in [i, i + \textit{dist}-1]$,我们都能够在 中找到一个字符串,使得 是这个字符串的前缀。这存在着单调性,我们可以使用二分查找来确定 。

Interview AiBox logo

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

试试 AiBox 面试助手arrow_forward
description

题目描述

给你一个字符串数组 words 和一个字符串 target

如果字符串 xwords 任意 字符串的 前缀,则认为 x 是一个 有效 字符串。

现计划通过 连接 有效字符串形成 target ,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target,则返回 -1

 

示例 1:

输入: words = ["abc","aaaaa","bcdef"], target = "aabcdabc"

输出: 3

解释:

target 字符串可以通过连接以下有效字符串形成:

  • words[1] 的长度为 2 的前缀,即 "aa"
  • words[2] 的长度为 3 的前缀,即 "bcd"
  • words[0] 的长度为 3 的前缀,即 "abc"

示例 2:

输入: words = ["abababab","ab"], target = "ababaababa"

输出: 2

解释:

target 字符串可以通过连接以下有效字符串形成:

  • words[0] 的长度为 5 的前缀,即 "ababa"
  • words[0] 的长度为 5 的前缀,即 "ababa"

示例 3:

输入: words = ["abcdef"], target = "xyz"

输出: -1

 

提示:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 5 * 104
  • 输入确保 sum(words[i].length) <= 105.
  • words[i]  只包含小写英文字母。
  • 1 <= target.length <= 5 * 104
  • target  只包含小写英文字母。
lightbulb

解题思路

方法一:字符串哈希 + 二分查找 + 贪心

由于本题数据规模较大,使用“字典树 + 记忆化搜索”的方法将会超时,我们需要寻找一种更高效的解法。

考虑从字符串 target\textit{target} 的第 ii 个字符开始,最远能够匹配的字符串长度,假设为 dist\textit{dist},那么对于任意 j[i,i+dist1]j \in [i, i + \textit{dist}-1],我们都能够在 words\textit{words} 中找到一个字符串,使得 target[i..j]\textit{target}[i..j] 是这个字符串的前缀。这存在着单调性,我们可以使用二分查找来确定 dist\textit{dist}

具体地,我们首先预处理出 words\textit{words} 中所有字符串的每个前缀的哈希值,按照前缀长度分组存储在 s\textit{s} 数组中。另外,将 target\textit{target} 的哈希值也预处理出来,存储在 hashing\textit{hashing} 中,便于我们查询任意 target[l..r]\textit{target}[l..r] 的哈希值。

接下来,我们设计一个函数 f(i)\textit{f}(i),表示从字符串 target\textit{target} 的第 ii 个字符开始,最远能够匹配的字符串长度。我们可以通过二分查找的方式确定 f(i)\textit{f}(i)

定义二分查找的左边界 l=0l = 0,右边界 r=min(ni,m)r = \min(n - i, m),其中 nn 是字符串 target\textit{target} 的长度,而 mmwords\textit{words} 中字符串的最大长度。在二分查找的过程中,我们需要判断 target[i..i+mid1]\textit{target}[i..i+\textit{mid}-1] 是否是 s[mid]\textit{s}[\textit{mid}] 中的某个哈希值,如果是,则将左边界 ll 更新为 mid\textit{mid},否则将右边界 rr 更新为 mid1\textit{mid}-1。二分结束后,返回 ll 即可。

算出 f(i)\textit{f}(i) 后,问题就转化为了一个经典的贪心问题,我们从 i=0i = 0 开始,对于每个位置 ii,最远可以移动到的位置为 i+f(i)i + \textit{f}(i),求最少需要多少次移动即可到达终点。

我们定义 last\textit{last} 表示上一次移动的位置,变量 mx\textit{mx} 表示当前位置能够移动到的最远位置,初始时 last=mx=0\textit{last} = \textit{mx} = 0。我们从 i=0i = 0 开始遍历,如果 ii 等于 last\textit{last},说明我们需要再次移动,此时如果 last=mx\textit{last} = \textit{mx},说明我们无法再移动,返回 1-1;否则,我们将 last\textit{last} 更新为 mx\textit{mx},并将答案加一。

遍历结束后,返回答案即可。

时间复杂度 O(n×logn+L)O(n \times \log n + L),空间复杂度 O(n+L)O(n + L)。其中 nn 是字符串 target\textit{target} 的长度,而 LL 是所有有效字符串的总长度。

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
class Hashing:
    __slots__ = ["mod", "h", "p"]

    def __init__(self, s: List[str], base: int, mod: int):
        self.mod = mod
        self.h = [0] * (len(s) + 1)
        self.p = [1] * (len(s) + 1)
        for i in range(1, len(s) + 1):
            self.h[i] = (self.h[i - 1] * base + ord(s[i - 1])) % mod
            self.p[i] = (self.p[i - 1] * base) % mod

    def query(self, l: int, r: int) -> int:
        return (self.h[r] - self.h[l - 1] * self.p[r - l + 1]) % self.mod


class Solution:
    def minValidStrings(self, words: List[str], target: str) -> int:
        def f(i: int) -> int:
            l, r = 0, min(n - i, m)
            while l < r:
                mid = (l + r + 1) >> 1
                sub = hashing.query(i + 1, i + mid)
                if sub in s[mid]:
                    l = mid
                else:
                    r = mid - 1
            return l

        base, mod = 13331, 998244353
        hashing = Hashing(target, base, mod)
        m = max(len(w) for w in words)
        s = [set() for _ in range(m + 1)]
        for w in words:
            h = 0
            for j, c in enumerate(w, 1):
                h = (h * base + ord(c)) % mod
                s[j].add(h)
        ans = last = mx = 0
        n = len(target)
        for i in range(n):
            dist = f(i)
            mx = max(mx, i + dist)
            if i == last:
                if i == mx:
                    return -1
                last = mx
                ans += 1
        return ans
speed

复杂度分析

指标
时间complexity is O(N * L * W) where N is target length, L is maximum word length, and W is number of words, optimized with prefix hashing. Space complexity is O(N + total prefix storage) for DP and prefix structures.
空间Depends on the final approach
psychology

面试官常问的追问

外企场景
  • question_mark

    Mentions state transition dynamic programming and prefix concatenation strategy.

  • question_mark

    Asks about optimizing substring checks and reducing DP iteration overhead.

  • question_mark

    Explores edge cases where target cannot be formed or words are very long.

warning

常见陷阱

外企场景
  • error

    Failing to consider all valid prefixes leads to incorrect DP updates.

  • error

    Using naive substring comparisons results in TLE for large input sizes.

  • error

    Returning wrong index offset or missing initial DP initialization at zero.

swap_horiz

进阶变体

外企场景
  • arrow_right_alt

    Limit reuse of words to at most once per concatenation, changing DP transitions.

  • arrow_right_alt

    Count all distinct ways to form target instead of minimum number.

  • arrow_right_alt

    Extend to allow words containing wildcards that match multiple characters.

help

常见问题

外企场景

形成目标字符串需要的最少字符串数 II题解:状态·转移·动态规划 | LeetCode #3292 困难