LeetCode——139. 单词拆分
给定一个非空字符串 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. 单词拆分的更多相关文章
- Java实现 LeetCode 139 单词拆分
139. 单词拆分 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可 ...
- LeetCode 139. 单词拆分(Word Break)
139. 单词拆分 139. Word Break
- Leetcode 139.单词拆分
单词拆分 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可以假设字典 ...
- leetcode 139 单词拆分(word break)
一开始的错误答案与错误思路,幻想直接遍历得出答案: class Solution { public: bool wordBreak(string s, vector<string>& ...
- [LeetCode] 139. 单词拆分(DP)
题目 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆分时可以重复使用字典中的单词. 你可以假设字典中没 ...
- Java实现LeetCode 139 单词拆分
public boolean wordBreak(String s, List<String> wordDict) { if(s.length() == 0){ return false; ...
- [LeetCode] 140. 单词拆分 II
题目链接 : https://leetcode-cn.com/problems/word-break-ii/ 题目描述: 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符 ...
- leetcode 140 单词拆分2 word break II
单词拆分2,递归+dp, 需要使用递归,同时使用记忆化搜索保存下来结果,c++代码如下 class Solution { public: //定义一个子串和子串拆分(如果有的话)的映射 unorder ...
- Java实现 LeetCode 140 单词拆分 II(二)
140. 单词拆分 II 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中.返回所有这些可能的句子. 说明: 分 ...
随机推荐
- 吴裕雄--天生自然java开发常用类库学习笔记:多线程基础编程
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类 private String name ; // 表示线程的名称 public ...
- Java If ... Else
章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...
- Easy Climb UVA - 12170 滚动dp +离散化+ 单调队列优化
E.Easy Climb Somewhere in the neighborhood we have a very nice mountain that gives a splendid view o ...
- bzoj 2451 Uyuw's Concert
裸的半平面交.感觉这些东西,纯属在考代码能力啊.. #include<cstdio> #include<algorithm> #include<cmath> #de ...
- 020-PHP浏览目录
<?php // 使用表格浏览目录的结构 print("<TABLE BORDER= '1'>"); // 创建表格的头 print("<TR&g ...
- 132-PHP子类和父类同名函数的调用
<?php class father{ //定义father类 public function cook(){ return '烹饪'; } } class son extends father ...
- PGSQL基本操作语句
; --更新数据 ,,) ; --插入数据 ORDER BY app_name,flag asc/desc ; --查询数据并且排序 offset ; --查询起点0开始查询,返回5条数据 ORDER ...
- Redis的安装并配置快捷启动
Redis 安装 1.下载 wget http://download.redis.io/releases/redis-5.0.5.tar.gz 2.解压 tar -zxvf redis-5.0.5.t ...
- 详解BurpSuite软件 请求包 HTTP (9.23 第十天)
HTTP协议基础 HTTP:HyperText Transfer Protocol,超文本传输协议 1.协议特点: 简单快速,请求方式get post head等8中请求方式 无连接(一次请求就断开) ...
- oracle 查询char类型的数据
曾经遇到一个坑. ';//使用PLSQL工具 能查出结果 偏偏在java代码里面查询不出结果. select taskdate from taskinfo where taskdate='201808 ...