这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战
这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。
159. 单词拆分(word-break)
标签
- 中等
- DFS + 回溯
- BFS + 回溯
题目
给定一个非空字符串 s
和一个包含非空单词
的列表 wordDict
,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
- 拆分时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
示例 1
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成
"apple pen apple"
注意你可以重复使用字典中的单词。
示例 3
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
基本思路
这种字符串类型首先肯定是可以用动态规划解决的。但我们今天用另外两种方式去看问题。
比如大问题就是整个字符串能不能被切分,那么我们可以设个 dp[i]
为
s[0..i]
是字符串前i
个字符,看看他能不能被拆分- 然后 dp[s.length-1] 其实就是 我们的求解
当然 我们今天用的是另两种思路。
1. DFS + 回溯
我们总结过 DFS + 回溯
问题的 模板 可以先看下再思考我们现在的问题
- 我们可以DFS遍历所有字符
- 设置一个 start 指针,我们只要判断
-
s[0] ~ s[start]
是单词,如果这个不是单词
,直接剪枝, 进行回溯
-
- 剩余子串递归判断是不是可以分隔成单词
-
DFS 写法实现
var wordBreak = function(s, wordDict) {
let res = false, len = s.length
// 用一个数组记录历史已经计算过的情况,做缓存
let cache = new Array(len)
const dfs = (start) => {
// 递归出口,说明到最后了,都可以分隔,直接 true
if (start === len) {
return true
}
// 缓存中有,直接用缓存的
if (cache[start]) {
return cache[start]
}
for (let i = start + 1; i <= len; i++) {
const prefix = s.slice(start, i)
// 两个条件 一个是前缀是单词,还有剩余子串能被分割, 满足,返回 true
if (wordDict.includes(prefix) && dfs(i)) {
cache[start] = true;
return true;
}
}
// 否则当前递归结果为 false
cache[start] = false;
return false;
}
// 从第一个元素 下标为 0 开始 dfs
res = dfs(0)
return res
};
let s = "applepenapple", wordDict = ["apple", "pen"]
console.log(wordBreak(s, wordDict))
这个方法偶尔会超时,可以优化 如 set 表示字典等, 我们来看下比较好的 BFS
2. BFS + 回溯
- BFS 核心其实就是种一个种子在队列中,比如开始时 放 0,作为最早的下标,分别跟其他每个元素相加,看是否能与单词匹配,如能匹配,说明这就是下一颗种子,入队列,继续考察这些种子的情况,直到队列清空, 我们看下面代码。
BFS 写法实现
var wordBreak = function(s, wordDict) {
let len = s.length
let cache = new Array(len)
// 这次我们不用 include 用 set 的 has
const wordSet = new Set(wordDict);
// BFS 使用 queue
const queue = [0];
while (queue.length) {
const start = queue.shift();
// 访问过的跳过
if (cache[start]) continue;
cache[start] = true;
for (let i = start + 1; i <= len; i++) {
const prefix = s.slice(start, i);
if (wordSet.has(prefix)) {
// 前缀是单词,且 i 没超过尾部, 就继续往下划分,把 i 入列作为下轮种子
if (i < len) {
queue.push(i)
} else {
// 到最后了,没有剩余,且前面都能被分割 返回true
return true
}
}
}
}
return false
};
let s = "applepenapple", wordDict = ["apple", "pen"]
console.log(wordBreak(s, wordDict))
另外向大家着重推荐下这个系列的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列
今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 点击此处交个朋友
Or 搜索我的微信号infinity_9368
,可以聊天说地
加我暗号 "天王盖地虎" 下一句的英文
,验证消息请发给我
presious tower shock the rever monster
,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧