给定一个非空字符串 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-break

【leetcode-55】跳跃游戏类似,回溯超时,转动态规划

我的解法(超时):

public class Solutiontemp {
public boolean wordBreak(String s, List<String> wordDict) {
for (int i=0;i<wordDict.size();i++) {
if (match(s,wordDict.get(i))) {
if (s.length() == wordDict.get(i).length()) {
return true;
}
if (wordBreak(s.substring(wordDict.get(i).length()),wordDict)){
return true;
}
}
}
return false;
}
public boolean match(String s,String word) {
if (s.length() < word.length()) {
return false;
}
for (int i=0;i<word.length();i++) {
if (s.charAt(i) != word.charAt(i)) {
return false;
}
}
return true;
}

leetcode官方解答:

方法 1:暴力

超时

最简单的实现方法是用递归和回溯。为了找到解,我们可以检查字典单词中每一个单词的可能前缀,如果在字典中出现过,那么去掉这个前缀后剩余部分回归调用。同时,如果某次函数调用中发现整个字符串都已经被拆分且在字典中出现过了,函数就返回 true 。

public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
return word_Break(s, new HashSet(wordDict), 0);
}
public boolean word_Break(String s, Set<String> wordDict, int start) {
if (start == s.length()) {
return true;
}
for (int end = start + 1; end <= s.length(); end++) {
if (wordDict.contains(s.substring(start, end)) && word_Break(s, wordDict, end)) {
return true;
}
}
return false;
}
}

复杂度分析

时间复杂度:O(n^n)。考虑最坏情况 ss = \text{aaaaaaa}aaaaaaa 。每一个前缀都在字典中,此时回溯树的复杂度会达到 n^n。

空间复杂度:O(n)。回溯树的深度最深达到 n。

方法 2:记忆化回溯

在先前的方法中,我们看到许多函数调用都是冗余的,也就是我们会对相同的字符串调用多次回溯函数。为了避免这种情况,我们可以使用记忆化的方法,其中一个 memomemo 数组会被用来保存子问题的结果。每当访问到已经访问过的后缀串,直接用 memomemo 数组中的值返回而不需要继续调用函数。

通过记忆化,许多冗余的子问题可以极大被优化,回溯树得到了剪枝,因此极大减小了时间复杂度。

public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
return word_Break(s, new HashSet(wordDict), 0, new Boolean[s.length()]);
}
public boolean word_Break(String s, Set<String> wordDict, int start, Boolean[] memo) {
if (start == s.length()) {
return true;
}
if (memo[start] != null) {
return memo[start];
}
for (int end = start + 1; end <= s.length(); end++) {
if (wordDict.contains(s.substring(start, end)) && word_Break(s, wordDict, end, memo)) {
return memo[start] = true;
}
}
return memo[start] = false;
}
}

复杂度分析

时间复杂度:O(n^2) 。回溯树的大小最多达到 n^2 。

空间复杂度:O(n)。回溯树的深度可以达到 n级别。

方法 3:使用宽度优先搜索

另一个方法是使用宽度优先搜索。将字符串可视化成一棵树,每一个节点是用 endend 为结尾的前缀字符串。当两个节点之间的所有节点都对应了字典中一个有效字符串时,两个节点可以被连接。

为了形成这样的一棵树,我们从给定字符串的第一个字符开始(比方说 ss ),将它作为树的根部,开始找所有可行的以该字符为首字符的可行子串。进一步的,将每一个子字符串的结束字符的下标(比方说 ii)放在队列的尾部供宽搜后续使用。

每次我们从队列最前面弹出一个元素,并考虑字符串 s(i+1,end)s(i+1,end) 作为原始字符串,并将当前节点作为树的根。这个过程会一直重复,直到队列中没有元素。如果字符串最后的元素可以作为树的一个节点,这意味着初始字符串可以被拆分成多个给定字典中的子字符串。

public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet=new HashSet(wordDict);
Queue<Integer> queue = new LinkedList<>();
int[] visited = new int[s.length()];
queue.add(0);
while (!queue.isEmpty()) {
int start = queue.remove();
if (visited[start] == 0) {
for (int end = start + 1; end <= s.length(); end++) {
if (wordDictSet.contains(s.substring(start, end))) {
queue.add(end);
if (end == s.length()) {
return true;
}
}
}
visited[start] = 1;
}
}
return false;
}
}

复杂度分析

时间复杂度:O(n^2)。对于每个开始的位置,搜索会直到给定字符串的尾部结束。

空间复杂度:O(n)。队列的大小最多 n。

方法 4:使用动态规划

动态规划思路:用一个数组res来存状态,0代表以当前索引对应的字符为结尾的字符串不可被完全拆分,1代表可以拆分。
之后遍历字符串的每个字符,对于每个字符,用一个指针p向前找距离在maxlen以内的字符串,当满足以下两个条件,即说明以当前字符结尾的字符串是可以被拆分的:

p指向的位置的状态是1(说明0到p的字符串是可以完全拆分的),且s[p+1:i]在字典里面
p指向的位置的状态是字符串的开头,且s[p+1:i]在字典里面

leetcode中文翻译得很晦涩:

public class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordDictSet=new HashSet(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}

【1】【leetcode-139】【回溯超时、动态规划】单词拆分的更多相关文章

  1. [LeetCode] 140. Word Break II 单词拆分II

    Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add space ...

  2. [LeetCode] 139. Word Break 单词拆分

    Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine ...

  3. Java实现 LeetCode 139 单词拆分

    139. 单词拆分 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可 ...

  4. LeetCode 139. 单词拆分(Word Break)

    139. 单词拆分 139. Word Break

  5. Leetcode 139.单词拆分

    单词拆分 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可以假设字典 ...

  6. [LeetCode] 140. 单词拆分 II

    题目链接 : https://leetcode-cn.com/problems/word-break-ii/ 题目描述: 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符 ...

  7. Leetcode之回溯法专题-212. 单词搜索 II(Word Search II)

    Leetcode之回溯法专题-212. 单词搜索 II(Word Search II) 给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词. 单 ...

  8. Leetcode之回溯法专题-79. 单词搜索(Word Search)

    Leetcode之回溯法专题-79. 单词搜索(Word Search) 给定一个二维网格和一个单词,找出该单词是否存在于网格中. 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元 ...

  9. leetcode 140 单词拆分2 word break II

    单词拆分2,递归+dp, 需要使用递归,同时使用记忆化搜索保存下来结果,c++代码如下 class Solution { public: //定义一个子串和子串拆分(如果有的话)的映射 unorder ...

随机推荐

  1. MP构造器

    官方文档:https://baomidou.gitee.io/mybatis-plus-doc/#/wrapper 实体包装器,用于处理 sql 拼接,排序,实体参数查询等! 补充说明: 使用的是数据 ...

  2. mysql语法之union

    UNION的语法结构: SELECT ... UNION [ ALL | DISTINCT ]  SELECT .... [ UNION [ ALL | DISTINCT ] SELECT ..... ...

  3. Linux的yum管理

    前面介绍了软件的管理的方式rpm.但有个缺点,rpm不能解决依赖. 下面介绍的yum软件管理.可以完美的解决这个问题. 使用yum的方式管理rpm软件         优势:自动解决软件的依赖关系   ...

  4. ChengDu University Mental Health Test 需求分析文档

    ChengDu University Mental Health Website 需求分析文档 V4.0 编制人:刘雷,黄凯 日期:2019/4/28 版本修订历史记录: 版本 日期 修改内容 作者 ...

  5. 豆瓣读书isbn 查询

    最近学习微信小程序,做一个类似"书库"的小demo,大致流程使用摄像头获取书本后面的isbn,通过豆瓣读书API得到书本介绍.豆瓣评分.图书评论等信息,然鹅https://api. ...

  6. 线程queue、线程进程池,协程

    线程queue import queue q = queue.Queue() #先进先出 q = queue.LifoQueue() #先进后出 t = queue.PriorityQueue() # ...

  7. js 浮点数计算精度不准确问题

    或许很多人都遇到过,js 对小数的加.减.乘.除时经常得到一些奇怪的结果! 比如 :0.1 + 0.2 = 0.3  ? 这么一个简单的计算,当你用js 计算时会发现结果是:0.30000000000 ...

  8. 微信(十一) 使用调试助手申请设备ID和报备流程

    以下流程模拟了一个设备,从微信硬件申请一个产品IP,对此ID进行报备生效,查询自己的绑定主人,给绑定主人发送消息的一系列http请求流程. 1 获取微信密钥 下面需要在公众号设备电脑IP白名单的电脑才 ...

  9. 云服务器使用: 为域名申请SSl证书

    注:咱们平时访问网站 有的是http  有的是 https https就是说使用了SSL访问 然后就是等着..... 假设可以了 然后就会有各个软件使用的证书,然后根据自己安装的软件下载证书

  10. 用luks方式对磁盘进行加密以及加密磁盘的自动挂载

    1.关于luks加密 LUKS(Linux Unified Key Setup)为Linux硬盘分区加密提供了一种标准,它不仅能通用于不同的 Linux发行版本,还支持多用户/口令.因为它的加密密钥独 ...