前端算法必刷题系列[87]

180 阅读3分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

159. 单词拆分(word-break)

标签

  • 中等
  • DFS + 回溯
  • BFS + 回溯

题目

leetcode 传送门

给定一个非空字符串 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 指针,我们只要判断
      1. s[0] ~ s[start] 是单词,如果这个不是单词直接剪枝, 进行回溯
      1. 剩余子串递归判断是不是可以分隔成单词

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,我看到就通过,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考