3291. 形成目标字符串需要的最少字符串数 I

给你一个字符串数组 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 * 10^3
  • 输入确保 sum(words[i].length) <= 10^5
  • words[i] 只包含小写英文字母。
  • 1 <= target.length <= 5 * 10^3
  • target 只包含小写英文字母。

字典树+动态规划(记忆化搜索)

超时了,差两个用例

看题解评论区说:

记忆化搜索没有写成递推的话是有这个问题的,主要原因是即便有记忆化,也有多运行的地方,递推不会运行memo的判断和调用。效率有差异。建议写成递推就可以真正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
54
55
56
class Trie {
class TrieNode {
boolean end;
// int cnt; // 维护节点数量
TrieNode[] children = new TrieNode[26];
}
TrieNode root = new TrieNode();
void insert(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
int index = (int) c - 97;
if (cur.children[index] == null) {
cur.children[index] = new TrieNode();
}
cur = cur.children[index];
}
cur.end = true;
}
boolean search(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
int index = (int) c - 97;
if (cur.children[index] == null) return false;
cur = cur.children[index];
}
return true;
}
}
class Solution {
public int minValidStrings(String[] words, String target) {
Trie t = new Trie();
for (String word : words) {
t.insert(word);
}
int n = target.length();
int memo[] = new int[n];
Arrays.fill(memo, (int)2e9);
int res = dfs(0, t, target, memo);
return res >= (int)2e9 ? -1 : res;
}
private int dfs(int i, Trie t, String target, int memo[]) {
int n = target.length();
if (t.search(target.substring(i))) return 1;
if (i >= n) return 0;
if (memo[i] != (int)2e9) return memo[i];
int res = (int)1e9;
Trie.TrieNode cur = t.root;
for (int k = i; k < n; k++) {
int idx = (int)target.charAt(k)-97;
if (cur.children[idx] == null) break;
cur = cur.children[idx];
res = Math.min(res, dfs(k+1, t, target, memo) + 1);
}
return memo[i] = res;
}
}

字典树+动态规划(递推)

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
class Trie {
class TrieNode {
boolean end;
// int cnt; // 维护节点数量
TrieNode[] children = new TrieNode[26];
}
TrieNode root = new TrieNode();
void insert(String word) {
TrieNode cur = root;
for (char c : word.toCharArray()) {
int index = (int) c - 97;
if (cur.children[index] == null) {
cur.children[index] = new TrieNode();
}
cur = cur.children[index];
}
cur.end = true;
}
int search(String target, int idx) {
TrieNode cur = root;
int n = target.length();
int cnt = 0;
for (int i = idx; i < n; i++) {
int index = (int)target.charAt(i) - 97;
if (cur.children[index] == null) break;
cur = cur.children[index];
cnt++;
}
return cnt;
}
}
class Solution {
public int minValidStrings(String[] words, String target) {
Trie t = new Trie();
for (String word : words) {
t.insert(word);
}
int n = target.length();
// f[i]: 对于字符串target[:i)的所需连接的最少字符串数量
int f[] = new int[n+1];
Arrays.fill(f, (int)1e9);
f[0] = 0;
for (int i = 0; i < n; i++) {
if (f[i] == (int)1e9) continue;
int cnt = t.search(target, i);
for (int j = 0; j < cnt && i+1+j <= n; j++) {
f[i+1+j] = Math.min(f[i+1+j], f[i] + 1);
}
}
return f[n] == (int)1e9 ? -1 : f[n];
}
}