139 单词拆分
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
问题分析
Round One
- 遍历收集所有的 s 可以被拆分的组合
- 遍历组合,判断是否有某个组合中的所有子串都在 dict 中
这种解法看上去没有问题,关键点在于收集所有的 s 可以被拆分的组合,这里显然需要深度优先和递归。但是如何实现第 1 步,却把我卡住了。没有理清代码的关系。一直卡在了那里。
这里暴露了我的一个问题,easy fall into a promble and can’t walk out.
不知道是不是其他人也有会有这个问题呢?其实在问题卡住的时候,无非是两种做法。
- 死磕,把卡住的问题解决(这样也不能算是卡住了)
- 换一种思路,前提是能想到别的思路
综合上面的两个解决方案,不难发现,本质无非是你足够聪明(可以把卡住的问题解决掉),或是你见多识广(可以换一种方式解决问题)。无论哪一种,都是能力的体现。
不得不说,是否聪明比较客观,无法改变,但是思路的转化是可以通过经验的积累来实现的。
Round Two
在参考了 leetcode 上的 solutions 之后,获得了很大的启发。将问题分解,先找到一个,然后再对子问题求解,如果子问题也满足条件,那么这个组合就是 ok 的。
- 一个指针遍历字符串,验证指针前半子串 s1 是否在 dict 中,不存在就移动指针。
- 如果存在,那么说明已经分解出一个单词,然后对指针的后半子串 s2 进行同样的验证操作(递归)。
- 直到后半子串 s2 的长度为 0(递归基),说明前面的子串都可以在 dict 中找到了。
Round Three
上面的解法基本解决了问题,但是存在很多重复计算。举个例子,一个字符串abcd
,如过
- a | bcd
- a | b | cd
- a | b | c | d
- a | bc | d
- a | b | c | d
- a | bcd
- a | b | cd
- ab | cd
- ab | c | d
- ab | cd
可以看出 c
和 d
的切分进行了很多次。对于重复的计算,简单的做法就是记录一下,这个子串是否已经切分过了。如果切分过了,就直接返回
代码实现
非优化
1 | function wordBreak(s: stirng, dict: string[]): boolean { |
优化 - memo 记录
1 | function canBreak(str: string, dict: string[], memo: Set<string>) { |
优化 - dp
- 定义状态 dp[i]代表子串 s[0:i-1]是否可以被拆分。
dp[i] = dp[j] && dict.has(s.slice(j,i)) j[0,i-1]
- 将 s[0:i-1]以 j 分为两部分,如果 dp[j]为 true,说明 s[0,j-1]可以拆分
- 如果 s[j:i-1]在 dict 中,则说明这两部分合起来也可以拆分(s[0:j-1],s[j,i-1])
- 最终求的就是 dp[s.length];
举个例子:s=leetcode; dict=['leet','code']; 那么dp[3]=true // 因为s[0,3]='leet'; 而且s[4,7]='code' 存在于dict中所以可以被拆分
1 | function wordBreak(s: string, dict: string[]): boolean { |