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,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中.返回所有这些可能的句子. 说明: 分 ...
随机推荐
- linux后台运行jar
1. 在linux服务器上运行Jar文件时通常的方法是: $ java -jar test.jar 这种方式当ssh窗口关闭时,程序中止运行,或者是运行时没法切出去执行其他任务 2. 保持程序后台运行 ...
- TX2开发板Ubuntu16.04设置静态IP
TX2开发板Ubuntu16.04设置静态IP https://www.cnblogs.com/qilai/p/11285445.html 首先打开一个Terminal输入 ifconfig 查看自 ...
- C++面试常见问题——01预处理与宏定义
C++面试常见问题--------01预编译和宏的使用 C++预处理器 预处理器是一些指令,它将指示编译器在实际编译之前需要完成的预处理.预处理必须要在对程序进行词法与语义分析.代码生成与优化等通常的 ...
- gpasswd命令 gpasswd -a user_name group_name
最后一句 gpasswd命令是Linux下工作组文件/etc/group和/etc/gshadow管理工具. 语法 gpasswd(选项)(参数) 选项 -a:添加用户到组: -d:从组删除用户: - ...
- 八十七、SAP中ALV事件之一,事件的声明
一.我们双击"REUSE_ALV_GRID_DISPLAY",来到SE37界面, 二,来到这儿,点击SLIS_T_EVENT, 三.可以看到SLIS_T_EVENT的定义,有一个n ...
- 033-PHP取1-100的随机数
<?php // 生成一个随机数 // 从1到100中取得随机数 for ($index = 0; $index < 100; $index++) { $number = (rand() ...
- 148-PHP strip_tags函数,剥去字符串中的 HTML 标签(二)
<?php //定义一段包含PHP代码的字符串 $php=<<<PHP 这里是PHP代码的开始 <?php echo "hello!"; PHP; $ ...
- 2. FTP 服务器安装
vsftp 安装(linux) Linux : 安装,创建虚拟用户,配置,防火墙设置 1. 安装 执行yum -y install vsftpd 注意: (1) 是否使用sudo权限执行请根据您具体环 ...
- 前端安全之 XSS攻击
参看: XSS的原理分析与解剖 前端安全 -- XSS攻击 web大前端开发中一些常见的安全性问题 1.前言 XSS 是面试时,hr提出来给我的,然后大体的浏览一遍,今天才查阅资料大体了解了它. XS ...
- MySQL5.7忘记密码解决方案
第一种方法 首先是网上普遍见到的 关闭服务 windows下在C:\ProgramData(隐藏文件夹)\MySQL\MySQL Server 5.7\my.ini中的[mysqld]下添加skip- ...