前言
动态规划专题,从简到难通关动态规划。
每日一题
今天的题目是 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
提示:
1 <= s.length <= 3001 <= wordDict.length <= 10001 <= wordDict[i].length <= 20s和wordDict[i]仅有小写英文字母组成wordDict中的所有字符串 互不相同
题解
回溯算法
- 划分子串 对于字符串s,我们可以在任意位置对其进行划分,得到一个新的字串。例如:“leetcode”,可以得到新的字串"leet"和"code"。
- 检查拆分 对于划分得到的字串,我们需要检查其是否可以由wordDict中的单词组合而成。如果可以进行拆分划分,直到整个字符串都可以被拆分成由单词组成的形式。如果不能进行拆分,则回溯到上一次进行拆分的位置,并重新进行拆分的处理操作。
- 可以使用了memory数组来记录每个startIndex位置开始的子串是否可以被拆分成符合要求的形式。当发现当前子串被处理过时,直接使用已知结果,避免重复处理同一个子问题,减少计算量。
function wordBreak(s: string, wordDict: string[]): boolean {
const wordSet = new Set(wordDict);
const memory = new Array(s.length).fill(-1);
function backtracking(startIndex) {
if (startIndex >= s.length) {
return true;
}
if (memory[startIndex] !== -1) {
return memory[startIndex] === 1;
}
for (let i = startIndex; i < s.length; i++) {
const word = s.substring(startIndex, i + 1);
if (wordSet.has(word) && backtracking(i + 1)) {
memory[startIndex] = 1;
return true;
}
}
memory[startIndex] = 0;
return false;
}
return backtracking(0);
}
动态规划
- 确定dp数组以及下标的含义
我们可以定义 dp[i] 表示字符串 s 前 i 个字符是否可以由字典中的单词拼接而成。由于字典中的单词可以重复使用,所以这是一个完全背包问题。
- 确定递推公式
对于每个i,我们要枚举从0到i-1的j值,表示将字符串s分为左右两部分,前面的j位和后面的i-j位。如果前面的j位可以被单词字典中的某个单词匹配,并且dp[j]为true,则说明前i位可以被完全匹配。因此有以下递推公式:
dp[i] = dp[j] && wordSet.has(s.substring(j, i));
- dp数组如何初始化
由于s为空字符串时,它一定可以被单词字典中的单词匹配,因此有dp[0] = true。其余位置的默认值为false,因为我们需要将它们递推得到。
- 确定遍历顺序
外层循环遍历字符串s的每个位置,即从1到s.length。内层循环遍历从0到i-1的每个值,表示将字符串s分为左右两部分。由于我们需要判断前面的j位是否可以被分解成字典中的单词,因此内层循环需要在外层循环之前遍历。
- 举例推导dp[i]
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 T F F F T F F F T
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| dp[i] | T | F | F | F | T | F | F | F | T |
代码:
function wordBreak(s: string, wordDict: string[]): boolean {
const wordSet = new Set(wordDict);
const dp = new Array(s.length + 1).fill(false);
dp[0] = true;
for (let i = 1; i <= s.length; i++) {
for (let j = 0; j < i; j++) {
const word = s.substr(j, i - j);
if (wordSet.has(word) && dp[j]) {
dp[i] = true;
break;
}
}
}
return dp[s.length];
}