Skip to content

Commit 152734a

Browse files
committed
140
1 parent 8f2f6b0 commit 152734a

5 files changed

+246
-39
lines changed

SUMMARY.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
* [98. Validate Binary Search Tree](leetCode-98-Validate-Binary-Search-Tree.md)
103103
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
104104
* [100. Same Tree](leetcode-100-Same-Tree.md)
105-
* [101 题到 139题](leetcode-101-200.md)
105+
* [101 题到 140题](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
@@ -141,4 +141,5 @@
141141
* [136. Single Number](leetcode-136-Single-Number.md)
142142
* [137*. Single Number II](leetcode-137-Single-NumberII.md)
143143
* [138. Copy List with Random Pointer](leetcode-138-Copy-List-with-Random-Pointer.md)
144-
* [139. Word Break](leetcode-139-Word-Break.md)
144+
* [139. Word Break](leetcode-139-Word-Break.md)
145+
* [140. Word Break II](leetcode-140-Word-BreakII.md)

leetCode-1-Two-Sum.md

+35-35
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@
99
简单粗暴些,两重循环,遍历所有情况看相加是否等于目标和,如果符合直接输出。
1010

1111
``` JAVA
12-
public int[] twoSum1(int[] nums, int target) {
13-
int []ans=new int[2];
14-
for(int i=0;i<nums.length;i++){
15-
for(int j=(i+1);j<nums.length;j++){
16-
if(nums[i]+nums[j]==target){
17-
ans[0]=i;
18-
ans[1]=j;
19-
return ans;
20-
}
21-
}
22-
}
23-
return ans;
12+
public int[] twoSum(int[] nums, int target) {
13+
int []ans=new int[2];
14+
for(int i=0;i<nums.length;i++){
15+
for(int j=(i+1);j<nums.length;j++){
16+
if(nums[i]+nums[j]==target){
17+
ans[0]=i;
18+
ans[1]=j;
19+
return ans;
20+
}
21+
}
2422
}
23+
return ans;
24+
}
2525
```
2626

2727

@@ -59,19 +59,19 @@ hash table !!!
5959
需要注意的地方是,还需判断找到的元素不是当前元素,因为题目里讲一个元素只能用一次。
6060

6161
``` JAVA
62-
public int[] twoSum2(int[] nums, int target) {
63-
Map<Integer,Integer> map=new HashMap<>();
64-
for(int i=0;i<nums.length;i++){
65-
map.put(nums[i],i);
66-
}
67-
for(int i=0;i<nums.length;i++){
68-
int sub=target-nums[i];
69-
if(map.containsKey(sub)&&map.get(sub)!=i){
70-
return new int[]{i,map.get(sub)};
71-
}
72-
}
73-
throw new IllegalArgumentException("No two sum solution");
74-
}
62+
public int[] twoSum(int[] nums, int target) {
63+
Map<Integer,Integer> map=new HashMap<>();
64+
for(int i=0;i<nums.length;i++){
65+
map.put(nums[i],i);
66+
}
67+
for(int i=0;i<nums.length;i++){
68+
int sub=target-nums[i];
69+
if(map.containsKey(sub)&&map.get(sub)!=i){
70+
return new int[]{i,map.get(sub)};
71+
}
72+
}
73+
throw new IllegalArgumentException("No two sum solution");
74+
}
7575
```
7676

7777
时间复杂度:比解法一少了一个 for 循环,降为 O(n)
@@ -83,16 +83,16 @@ public int[] twoSum2(int[] nums, int target) {
8383
看解法二中,两个 for 循环,他们长的一样,我们当然可以把它合起来。复杂度上不会带来什么变化,变化仅仅是不需要判断是不是当前元素了,因为当前元素还没有添加进 hash 里。
8484

8585
``` JAVA
86-
public int[] twoSum3(int[] nums, int target) {
87-
Map<Integer,Integer> map=new HashMap<>();
88-
for(int i=0;i<nums.length;i++){
89-
int sub=target-nums[i];
90-
if(map.containsKey(sub)){
91-
return new int[]{i,map.get(sub)};
92-
}
93-
map.put(nums[i], i);
94-
}
95-
throw new IllegalArgumentException("No two sum solution");
86+
public int[] twoSum(int[] nums, int target) {
87+
Map<Integer,Integer> map=new HashMap<>();
88+
for(int i=0;i<nums.length;i++){
89+
int sub=target-nums[i];
90+
if(map.containsKey(sub)){
91+
return new int[]{i,map.get(sub)};
92+
}
93+
map.put(nums[i], i);
94+
}
95+
throw new IllegalArgumentException("No two sum solution");
9696
}
9797
```
9898

leetcode-101-200.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,6 @@
7474

7575
<a href="leetcode-138-Copy-List-with-Random-Pointer.html">138. Copy List with Random Pointer</a>
7676

77-
<a href="leetcode-139-Word-Break.html">139. Word Break</a>
77+
<a href="leetcode-139-Word-Break.html">139. Word Break</a>
78+
79+
<a href="leetcode-140-Word-BreakII.html">140. Word Break II</a>

leetcode-139-Word-Break.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private boolean wordBreakHelper(String s, List<String> wordDict, String temp) {
9595

9696
发现上边的例子答案很明显是 `false`,因为 `s` 中的 `b` 字母在 `wordDict` 中并没有出现。
9797

98-
所以我们可以实现遍历一遍 `s``wordDict` ,从而确定 `s` 中的字符是否在 `wordDict` 中存在,如果不存在可以提前返回 `false`
98+
所以我们可以先遍历一遍 `s``wordDict` ,从而确定 `s` 中的字符是否在 `wordDict` 中存在,如果不存在可以提前返回 `false`
9999

100100
所以代码可以继续优化。
101101

leetcode-140-Word-BreakII.md

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# 题目描述(困难难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/140.png)
4+
5+
[139 题](https://leetcode.wang/leetcode-139-Word-Break.html) 的升级版。给一个字符串,和一些单词,找出由这些单词组成该字符串的所有可能,每个单词可以用多次,也可以不用。
6+
7+
完全按照 [139 题](https://leetcode.wang/leetcode-139-Word-Break.html) 的思路做了,大家可以先过去看一下。
8+
9+
# 解法一 动态规划
10+
11+
先考虑 `139 题` 最后一个解法,动态规划,看起来也比较简单粗暴。
12+
13+
> `dp[i]` 表示字符串 `s[0,i)` 能否由 `wordDict` 构成。
14+
>
15+
> ```java
16+
> public boolean wordBreak(String s, List<String> wordDict) {
17+
> HashSet<String> set = new HashSet<>();
18+
> for (int i = 0; i < wordDict.size(); i++) {
19+
> set.add(wordDict.get(i));
20+
> }
21+
> boolean[] dp = new boolean[s.length() + 1];
22+
> dp[0] = true;
23+
> for (int i = 1; i <= s.length(); i++) {
24+
> for (int j = 0; j < i; j++) {
25+
> dp[i] = dp[j] && wordDict.contains(s.substring(j, i));
26+
> if (dp[i]) {
27+
> break;
28+
> }
29+
> }
30+
> }
31+
> return dp[s.length()];
32+
> }
33+
> ```
34+
35+
这里修改的话,我们只需要用 `dp[i]` 表示字符串 `s[0,i)` 由 `wordDict` 构成的所有情况。
36+
37+
总体思想还是和之前一样的。
38+
39+
```java
40+
X X X X X X
41+
^ ^ ^
42+
0 j i
43+
先判断 j 到 i 的字符串在没在 wordDict 中
44+
然后把 0 到 j 的字符串由 wordDict 构成所有情况后边加空格再加上 j 到 i 的字符串即可
45+
```
46+
47+
结合上边的思想,然后把它放到循环中,考虑所有情况即可。
48+
49+
```java
50+
public List<String> wordBreak(String s, List<String> wordDict) {
51+
HashSet<String> set = new HashSet<>();
52+
for (int i = 0; i < wordDict.size(); i++) {
53+
set.add(wordDict.get(i));
54+
}
55+
List<List<String>> dp = new ArrayList<>();
56+
List<String> temp = new ArrayList<>();
57+
//空串的情况
58+
temp.add("");
59+
dp.add(temp);
60+
for (int i = 1; i <= s.length(); i++) {
61+
temp = new ArrayList<>();
62+
for (int j = 0; j < i; j++) {
63+
if (wordDict.contains(s.substring(j, i))) {
64+
//得到前半部分的所有情况然后和当前单词相加
65+
for (int k = 0; k < dp.get(j).size(); k++) {
66+
String t = dp.get(j).get(k);
67+
//空串的时候不加空格,也就是 j = 0 的时候
68+
if (t.equals("")) {
69+
temp.add(s.substring(j, i));
70+
} else {
71+
temp.add(t + " " + s.substring(j, i));
72+
}
73+
74+
}
75+
76+
}
77+
}
78+
dp.add(temp);
79+
}
80+
return dp.get(s.length());
81+
}
82+
```
83+
84+
遗憾的是,熟悉的问题又来了。
85+
86+
![](https://windliang.oss-cn-beijing.aliyuncs.com/140_2.jpg)
87+
88+
由于 `s` 中的 `b` 字母在 `wordDict` 中并没有出现,所以其实我们并不需要做那么多循环,直接返回空列表即可。
89+
90+
和之前一样,所以我们可以先遍历一遍 `s``wordDict` ,从而确定 `s` 中的字符是否在 `wordDict` 中存在,如果不存在可以提前返回空列表。
91+
92+
```java
93+
public List<String> wordBreak(String s, List<String> wordDict) {
94+
//提前进行一次判断
95+
HashSet<Character> set2 = new HashSet<>();
96+
for (int i = 0; i < wordDict.size(); i++) {
97+
String t = wordDict.get(i);
98+
for (int j = 0; j < t.length(); j++) {
99+
set2.add(t.charAt(j));
100+
}
101+
}
102+
for (int i = 0; i < s.length(); i++) {
103+
if (!set2.contains(s.charAt(i))) {
104+
return new ArrayList<>();
105+
}
106+
}
107+
108+
HashSet<String> set = new HashSet<>();
109+
for (int i = 0; i < wordDict.size(); i++) {
110+
set.add(wordDict.get(i));
111+
}
112+
List<List<String>> dp = new ArrayList<>();
113+
List<String> temp = new ArrayList<>();
114+
temp.add("");
115+
dp.add(temp);
116+
for (int i = 1; i <= s.length(); i++) {
117+
temp = new ArrayList<>();
118+
for (int j = 0; j < i; j++) {
119+
if (wordDict.contains(s.substring(j, i))) {
120+
for (int k = 0; k < dp.get(j).size(); k++) {
121+
String t = dp.get(j).get(k);
122+
if (t.equals("")) {
123+
temp.add(s.substring(j, i));
124+
} else {
125+
temp.add(t + " " + s.substring(j, i));
126+
}
127+
128+
}
129+
130+
}
131+
}
132+
dp.add(temp);
133+
}
134+
return dp.get(s.length());
135+
}
136+
```
137+
138+
遗憾的是,刚刚那个例子通过了,又出现了新的问题。
139+
140+
![](https://windliang.oss-cn-beijing.aliyuncs.com/140_3.jpg)
141+
142+
由于 `wordDict``b` 字母了,所以并没有提前结束,而是进了 `for` 循环。
143+
144+
再优化也想不到方法了,是我们的算法出问题了。因为 `139` 题中找到一个解以后就 `break` 了,而这里我们要考虑所有子串,所有的解,极端情况下时间复杂度达到了 `O(n³)`。还有一点致命的是,我们之前求的解最后可能并不需要。举个例子。
145+
146+
```java
147+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabad"
148+
["aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa","b","ba","de"]
149+
150+
我们之前求了 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 的所有组成可能,但由于剩余字符串 "bad" 不在 wordDict 中,所有之前求出来并没有用
151+
152+
又比如,我们之前求了 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab" 的所有组成可能,但由于剩余字符串 "ad" 不在 wordDict 中,所有之前求出来也并没有用
153+
```
154+
155+
针对这个问题,我们可以优化一下,也就是下边的解法二
156+
157+
# 解法二
158+
159+
我们直接用递归的方法,先判断当前字符串在不在 `wordDict` 中,如果在的话就递归的去求剩余字符串的所有组成可能。此外,吸取之前的教训,直接使用 `memoization` 技术,将递归过程中求出来的解缓存起来,便于之后直接用。
160+
161+
```java
162+
public List<String> wordBreak(String s, List<String> wordDict) {
163+
HashSet<String> set = new HashSet<>();
164+
for (int i = 0; i < wordDict.size(); i++) {
165+
set.add(wordDict.get(i));
166+
}
167+
return wordBreakHelper(s, set, new HashMap<String, List<String>>());
168+
}
169+
170+
private List<String> wordBreakHelper(String s, HashSet<String> set, HashMap<String, List<String>> map) {
171+
if (s.length() == 0) {
172+
return new ArrayList<>();
173+
}
174+
if (map.containsKey(s)) {
175+
return map.get(s);
176+
}
177+
List<String> res = new ArrayList<>();
178+
for (int j = 0; j < s.length(); j++) {
179+
//判断当前字符串是否存在
180+
if (set.contains(s.substring(j, s.length()))) {
181+
//空串的情况,直接加入
182+
if (j == 0) {
183+
res.add(s.substring(j, s.length()));
184+
} else {
185+
//递归得到剩余字符串的所有组成可能,然后和当前字符串分别用空格连起来加到结果中
186+
List<String> temp = wordBreakHelper(s.substring(0, j), set, map);
187+
for (int k = 0; k < temp.size(); k++) {
188+
String t = temp.get(k);
189+
res.add(t + " " + s.substring(j, s.length()));
190+
}
191+
}
192+
193+
}
194+
}
195+
//缓存结果
196+
map.put(s, res);
197+
return res;
198+
}
199+
```
200+
201+
#
202+
203+
按理说其实可以直接就想到解法二,但受之前写的题的影响,这种分治的问题,都最终能转成动态规划,所以先写了动态规划的思路,想直接一步到位,没想到反而遇到了问题,很有意思,哈哈。原因就是你求子问题的代价很大,而动态规划就是要求所有的子问题。而解决最终问题的时候,一些子问题其实是没有必要的。
204+

0 commit comments

Comments
 (0)