Skip to content

Commit 7d8768d

Browse files
committed
二刷139
1 parent faffb83 commit 7d8768d

File tree

9 files changed

+189
-29
lines changed

9 files changed

+189
-29
lines changed

docs/0139-word-break.adoc

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,97 @@
11
[#0139-word-break]
2-
= 139. Word Break
2+
= 139. 单词拆分
33

4-
{leetcode}/problems/word-break/[LeetCode - Word Break^]
4+
https://leetcode.cn/problems/word-break/[LeetCode - 139. 单词拆分 ^]
55

6-
TODO: 似乎明白了,又似乎不明白
6+
给你一个字符串 `s` 和一个字符串列表 `wordDict` 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 `s` 则返回 `true`
77

8-
Given a *non-empty* string _s_ and a dictionary _wordDict_ containing a list of *non-empty* words, determine if _s_ can be segmented into a space-separated sequence of one or more dictionary words.
8+
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
99

10-
*Note:*
10+
*示例 1:*
1111

12+
....
13+
输入: s = "leetcode", wordDict = ["leet", "code"]
14+
输出: true
15+
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
16+
....
1217

13-
* The same word in the dictionary may be reused multiple times in the segmentation.
14-
* You may assume the dictionary does not contain duplicate words.
18+
*示例 2:*
1519

20+
....
21+
输入: s = "applepenapple", wordDict = ["apple", "pen"]
22+
输出: true
23+
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
24+
注意,你可以重复使用字典中的单词。
25+
....
1626

17-
*Example 1:*
27+
*示例 3:*
1828

19-
[subs="verbatim,quotes,macros"]
20-
----
21-
*Input:* s = "leetcode", wordDict = ["leet", "code"]
22-
*Output:* true
23-
*Explanation:* Return true because `"leetcode"` can be segmented as `"leet code"`.
24-
----
29+
....
30+
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
31+
输出: false
32+
....
2533

26-
*Example 2:*
34+
*提示:*
2735

28-
[subs="verbatim,quotes,macros"]
29-
----
30-
*Input:* s = "applepenapple", wordDict = ["apple", "pen"]
31-
*Output:* true
32-
*Explanation:* Return true because `"`applepenapple`"` can be segmented as `"`apple pen apple`"`.
33-
Note that you are allowed to reuse a dictionary word.
34-
----
36+
* `+1 <= s.length <= 300+`
37+
* `+1 <= wordDict.length <= 1000+`
38+
* `+1 <= wordDict[i].length <= 20+`
39+
* `s``wordDict[i]` 仅由小写英文字母组成
40+
* `wordDict` 中的所有字符串 *互不相同*
3541
36-
*Example 3:*
3742
38-
[subs="verbatim,quotes,macros"]
39-
----
40-
*Input:* s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
41-
*Output:* false
42-
----
43+
== 思路分析
44+
45+
回溯+备忘录:
46+
47+
image::images/0139-01.png[{image_attr}]
48+
49+
动态规划
50+
51+
image::images/0139-02.png[{image_attr}]
52+
53+
image::images/0139-03.png[{image_attr}]
4354

55+
image::images/0139-04.png[{image_attr}]
4456

57+
我的思路:
58+
59+
stem:[dp\[i+j\] = dp\[i\] & s\[i, i+j\] in dict]
60+
61+
从 stem:[0] 到 stem:[i] 已经确定。从当前位置 stem:[i] 向前推进到 stem:[i+j],其中 stem:[j] 是某个一个单词的长度。
4562

4663
[[src-0139]]
64+
[tabs]
65+
====
66+
一刷::
67+
+
68+
--
4769
[{java_src_attr}]
4870
----
4971
include::{sourcedir}/_0139_WordBreak.java[tag=answer]
5072
----
73+
--
74+
75+
二刷(回溯+备忘录)::
76+
+
77+
--
78+
[{java_src_attr}]
79+
----
80+
include::{sourcedir}/_0139_WordBreak_20.java[tag=answer]
81+
----
82+
--
83+
84+
二刷(动态规划)::
85+
+
86+
--
87+
[{java_src_attr}]
88+
----
89+
include::{sourcedir}/_0139_WordBreak_21.java[tag=answer]
90+
----
91+
--
92+
====
93+
94+
== 参考资料
5195

96+
. https://leetcode.cn/problems/word-break/solutions/302779/shou-hui-tu-jie-san-chong-fang-fa-dfs-bfs-dong-tai/[139. 单词拆分 - 「手画图解」剖析三种解法: DFS, BFS, 动态规划^] -- 这个题解非常好,从回溯,到加备忘录
97+
. https://leetcode.cn/problems/word-break/solutions/50986/dong-tai-gui-hua-ji-yi-hua-hui-su-zhu-xing-jie-shi/[139. 单词拆分 - 动态规划+记忆化回溯 逐行解释 Python3^]

docs/images/0139-01.png

155 KB
Loading

docs/images/0139-02.png

6.96 KB
Loading

docs/images/0139-03.png

110 KB
Loading

docs/images/0139-04.png

96.3 KB
Loading

logbook/202503.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ endif::[]
66
:doc_base_url: link:../docs
77

88

9-
[cols="7,32,7,54",options="header"]
9+
[cols="7,30,7,56",options="header"]
1010
|===
1111
|序号 |题目 |题解 |备注
1212

@@ -340,6 +340,10 @@ endif::[]
340340
|{doc_base_url}/0221-maximal-square.adoc[题解]
341341
|✅ 动态规划。直接将结果存储在参数矩阵上。如果正方形想扩大,则左边,左上和上面三个都是正方形时才可以,可以直接去这三者中的最小值。
342342

343+
|{counter:codes2503}
344+
|{leetcode_base_url}/word-break/[139. 单词拆分^]
345+
|{doc_base_url}/0139-word-break.adoc[题解]
346+
|⭕️ 回溯+备忘录。首先想到的是回溯,但是超时(通过34/47的测试用例)。参考别人题解后,得到启发,加上备忘录通过。参考答案写出了动态规划的解法。*思考如何从基于回溯+备忘录转变为动态规划?*
343347

344348
|===
345349

src/main/java/com/diguage/algo/leetcode/_0139_WordBreak.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class _0139_WordBreak {
5252
* Memory Usage: 44.3 MB, less than 5.08% of Java online submissions for Word Break.
5353
*
5454
* Copy from: https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode/[单词拆分 - 单词拆分 - 力扣(LeetCode)]
55+
*
56+
* @author D瓜哥 · https://www.diguage.com
57+
* @since 2020-01-24 09:39
5558
*/
5659
public boolean wordBreak(String s, List<String> wordDict) {
5760
boolean[] dp = new boolean[s.length() + 1];
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.diguage.algo.leetcode;
2+
3+
import java.util.Comparator;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
8+
public class _0139_WordBreak_20 {
9+
// tag::answer[]
10+
/**
11+
* 纯回溯通过 34/47 的测试用例。在 35 个测试用例时超时。
12+
*
13+
* 加备忘录则可以通过。效率还挺高,时间和内存都超过了 80%+ 的用户
14+
*
15+
* @author D瓜哥 · https://www.diguage.com
16+
* @since 2025-04-19 15:41:59
17+
*/
18+
public boolean wordBreak(String s, List<String> wordDict) {
19+
Set<Integer> wordLengths = new HashSet<>();
20+
for (String string : wordDict) {
21+
wordLengths.add(string.length());
22+
}
23+
Set<String> set = new HashSet<>(wordDict);
24+
List<Integer> lengths = wordLengths.stream()
25+
.sorted(Comparator.reverseOrder()).toList();
26+
// 备忘录:记录剩余 i 个字符时,字符串是否可以拆分
27+
Boolean[] memo = new Boolean[s.length()];
28+
return backtrack(s, set, lengths, memo);
29+
}
30+
31+
private boolean backtrack(String s, Set<String> set,
32+
List<Integer> lengths, Boolean[] memo) {
33+
if (s == null || s.isEmpty() || set.contains(s)) {
34+
return true;
35+
}
36+
if (s.length() < lengths.getLast()) {
37+
return false;
38+
}
39+
for (Integer i : lengths) {
40+
if (s.length() < i) {
41+
continue;
42+
}
43+
String prefix = s.substring(0, i);
44+
if (!set.contains(prefix)) {
45+
continue;
46+
}
47+
String substring = s.substring(i);
48+
if (memo[substring.length() - 1] != null) {
49+
return memo[substring.length() - 1];
50+
}
51+
boolean track = backtrack(substring, set, lengths, memo);
52+
memo[substring.length() - 1] = track;
53+
if (track) {
54+
return true;
55+
}
56+
}
57+
return false;
58+
}
59+
// end::answer[]
60+
61+
public static void main(String[] args) {
62+
new _0139_WordBreak_20().wordBreak("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab",
63+
List.of("a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa", "aaaaaaa", "aaaaaaaa", "aaaaaaaaa", "aaaaaaaaaa"));
64+
}
65+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.diguage.algo.leetcode;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class _0139_WordBreak_21 {
7+
// tag::answer[]
8+
/**
9+
* @author D瓜哥 · https://www.diguage.com
10+
* @since 2025-04-19 17:13:31
11+
*/
12+
public boolean wordBreak(String s, List<String> wordDict) {
13+
boolean[] dp = new boolean[s.length() + 1];
14+
dp[0] = true;
15+
for (int i = 0; i < s.length(); i++) {
16+
// 如果前 i 个字符不能被拆分,则跳过该情况
17+
if (!dp[i]) {
18+
continue;
19+
}
20+
for (String word : wordDict) {
21+
int wordLen = word.length();
22+
if (s.length() < i + wordLen) {
23+
continue;
24+
}
25+
// 如果已经确认可以拆分,则跳过
26+
if (dp[i + wordLen]) {
27+
continue;
28+
}
29+
String substring = s.substring(i, i + wordLen);
30+
// dp[i+length] = dp[i] && s[i, i+length]∈dict
31+
dp[i + wordLen] = dp[i] && word.equals(substring);
32+
}
33+
}
34+
return dp[s.length()];
35+
}
36+
// end::answer[]
37+
38+
public static void main(String[] args) {
39+
new _0139_WordBreak_21().wordBreak("aaaaaaa",
40+
new ArrayList<>(List.of("aaaa", "aaa")));
41+
}
42+
}

0 commit comments

Comments
 (0)