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

记忆化回溯:

先把字典中的所有单词都存入 HashSet 中吧,这样我们就有了常数时间级的查找速度,我们得开始给字符串分段了,一个万能的做法是丢给递归函数,让其去递归求解,这里我们 suppose 递归函数会返回我们一个正确的值,如果返回的是 true 的话,表明我们现在分成的两段都在字典中,我们直接返回 true 即可,因为只要找出一种情况就行了。

遍历了所有的情况,优点是写法简洁,思路清晰,缺点是存在大量的重复计算。

所以我们需要进行优化,使用记忆数组 memo 来保存所有已经计算过的结果,再下次遇到的时候,直接从 cache 中取,而不是再次计算一遍。

符串是否可以拆分,初始化为 -1,表示没有计算过,如果可以拆分,则赋值为1,反之为0。

在之前讲的解法中,提到的是讲分成两段的后半段的调用递归函数,我们也可以不取出子字符串,而是用一个 start 变量,来标记分段的位置,这样递归函数中只需要从 start 的位置往后遍历即可,在递归函数更新记忆数组 memo 即可,参见代码如下:

c++

class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<int> memo(s.size(), -1);
return check(s, wordSet, 0, memo);
} bool check(string s, unordered_set<string>& wordSet, int start, vector<int>& memo) {
if (start >= s.size()) return true;
if (memo[start] != -1) return memo[start]; for (int i = start + 1; i <= s.size(); ++i) {
if (wordSet.count(s.substr(start, i-start)) && check(s, wordSet, i, memo)) {
return memo[start] = 1;
}
}
return memo[start] = 0;
}
};

java

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;
}
}

动态规划

DP 解法的两大难点,定义 dp 数组跟找出状态转移方程,先来看 dp 数组的定义,这里我们就用一个一维的 dp 数组,其中 dp[i] 表示范围 [0, i) 内的子串是否可以拆分,

注意这里 dp 数组的长度比s串的长度大1,是因为我们要 handle 空串的情况,我们初始化 dp[0] 为 true,然后开始遍历。

注意这里我们需要两个 for 循环来遍历,因为此时已经没有递归函数了,所以我们必须要遍历所有的子串,

我们用j把 [0, i) 范围内的子串分为了两部分,[0, j) 和 [j, i),其中范围 [0, j) 就是 dp[j],范围 [j, i) 就是 s.substr(j, i-j),其中 dp[j] 是之前的状态,我们已经算出来了,可以直接取,只需要在字典中查找 s.substr(j, i-j) 是否存在了,如果二者均为 true,将 dp[i] 赋为 true,并且 break 掉,此时就不需要再用j去分 [0, i) 范围了,因为 [0, i) 范围已经可以拆分了。最终我们返回 dp 数组的最后一个值,就是整个数组是否可以拆分的布尔值了,代码如下:

c++

class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size() + 1);
dp[0] = true;
for (int i = 0; i < dp.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (dp[j] && wordSet.count(s.substr(j, i - j))) {
dp[i] = true;
break;
}
}
}
return dp.back();
}
};

java

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()];
}
}

python

class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
import functools
@functools.lru_cache(None)
def back_track(s):
if(not s):
return True
res=False
for i in range(1,len(s)+1):
if(s[:i] in wordDict):
res=back_track(s[i:]) or res
return res
return back_track(s)

宽度优先搜索

下面我们从题目中给的例子来分析:
l
le e
lee ee e
leet
leetc eetc etc tc c
leetco eetco etco tco co o
leetcod eetcod etcod tcod cod od d
leetcode eetcode etcode tcode **code**
T F F F T F F F T

我们知道算法的核心思想是逐行扫描,每一行再逐个字符扫描,每次都在组合出一个新的字符串都要到字典里去找,如果有的话,则跳过此行,继续扫描下一行。

BFS其实本质跟递归的解法没有太大的区别,递归解法在调用递归的时候,原先的状态被存入了栈中,这里 BFS 是存入了队列中,使用 visited 数组来标记已经算过的位置,作用跟 memo 数组一样,从队列中取出一个位置进行遍历,把可以拆分的新位置存入队列中,遍历完成后标记当前位置,然后再到队列中去取即可,参见代码如下:

c++

class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> visited(s.size());
queue<int> q{{0}};
while (!q.empty()) {
int start = q.front(); q.pop();
if (!visited[start]) {
for (int i = start + 1; i <= s.size(); ++i) {
if (wordSet.count(s.substr(start, i - start))) {
q.push(i);
if (i == s.size()) return true;
}
}
visited[start] = true;
}
}
return false;
}
};

java

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;
}
}

python

class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
n=len(s)
dp=[False]*(n+1)
dp[0]=True
for i in range(n):
for j in range(i+1,n+1):
if(dp[i] and (s[i:j] in wordDict)):
dp[j]=True
return dp[-1]

LeetCode——139. 单词拆分的更多相关文章

  1. Java实现 LeetCode 139 单词拆分

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

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

    139. 单词拆分 139. Word Break

  3. Leetcode 139.单词拆分

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

  4. leetcode 139 单词拆分(word break)

    一开始的错误答案与错误思路,幻想直接遍历得出答案: class Solution { public: bool wordBreak(string s, vector<string>& ...

  5. [LeetCode] 139. 单词拆分(DP)

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

  6. Java实现LeetCode 139 单词拆分

    public boolean wordBreak(String s, List<String> wordDict) { if(s.length() == 0){ return false; ...

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

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

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

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

  9. Java实现 LeetCode 140 单词拆分 II(二)

    140. 单词拆分 II 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中.返回所有这些可能的句子. 说明: 分 ...

随机推荐

  1. 007.Oracle数据库 , 使用%进行模糊查询

    /*Oracle数据库查询日期在两者之间*/ SELECT PKID, OCCUR_DATE, ATA FROM LM_FAULT WHERE ( ( OCCUR_DATE >= to_date ...

  2. 在xampp下安装thinkphp5

    在xampp2016下安装thinkphp5 (本人的坑,说白了就是把thinkphp5的文件放到htdocs下) 1.把该xampp中的php文件夹路径设置进环境变量,这样才能全局运行compose ...

  3. Spring boot application.properties和 application.yml 初学者的学习

    来自于java尚硅谷教程 简单的说这两个配置文件更改配置都可以更改默认设置的值比如服务器端口号之类的,只需再文件中设置即可, properties可能是出现的比较早了,如果你不调你的默认编码,中文可能 ...

  4. (九)微信小程序---for指令

    对于数据是列表 wxml <view wx:for="{{dataList}}">{{index}}-{{item}}</view> 我们可以看到上面的代码 ...

  5. C++学习记录——(queue的清空)

    c++自带的queue并没有clear这个方法:所以只能自己写了. 一共三种(其实我决得就是两种): 第一种: 直接赋值 queue<int> MyQue; /* …… */ MyQue ...

  6. WebStorm 使用经验

        1.优点 1.1 可自动提示图片的宽高 1.2 标签名字可重构(改名) 1.3 css重命名 1.4 可把内联的style移到外部 1.5 可实现声明提升 1.6 设置项是可搜索的 1.7 有 ...

  7. C# 并行线程调用

    参考 一.异步委托开启线程 Action<int, int> a = add; a.BeginInvoke(, , null, null);//前两个是add方法的参数,后两个可以为空 C ...

  8. Web安全常见问题及解决方法

    关于Web安全,我们最早听到最多的就是SQL注入.例如用户在系统登录界面输入用户名和密码,提交以后,后端直接拿到数据就拼接SQL语句去查询数据库.如果在输入时进行了恶意的SQL拼装,那么最后生成的SQ ...

  9. javaweb利用ajax使登录窗口不跳转页面实现对账号密码的判断

    和上一篇判断用户名是否被占用不跳转页面类似!利用ajax实现跳转,要导入jquery文件库!具体代码我会贴出来,注释在里面!!可以观摩一手!(代码我也留下链接,如果失效,评论补发,代码可能导入也无法使 ...

  10. pycharm 设置项目的编译器

    设置编译器(interpreter) File---Setting--在搜索框输入(interpreter)