我是通过idea的leetcode插件获取题目做题的,所以题目描述基本都会有注释符号哈
这套题解题的时候参照LeetCode的其中一个方案,链接如下
单词划分 (官方解法,对应method2)
Java 动态规划优化:最长最短单词 (这个很dio,建议看, 对应 method3)
1. 题目描述
* [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 = "cats sand og", wordDict = ["cats", "dog", "sand", "and", "cat"]
* 输出: false
*
* Related Topics 动态规划
2. 思路分析
这个题目有点类似之前的分割回文数
我们的思路也是考虑从分割字符串,分为左串和右串入手,左串成功了,在判断右串是否也满足
以及引入一个到具体字符串的某一位是否满足拆分的结果集,最终我们可以通过结果集来返回我们想要的结果。
因此,我们的工作内容就是填充结果集,判断在某一个点切分开的字符串,是否是满足单词拆分的要求。引用一段method3方法的描述。
一般动态规划:
如果[0,k]可以被分词字典里的单词,检查[k + 1, i]也是字典里的单词,那么[0,i]也可以被分成字典里的单词 定义dp[i],dp[i]的含义是,前i个字符组成的子串能否被分割,令dp[0] = true 得出dp[i] = dp[k] && check(k + 1, i),check函数是判断该子串是否在字典内
需要注意,method1的 boolean数组记录的是 下标开始到字符串结束的结果是否符合要求,method2 和 method3 的数组记录的是 字符串的第几位到起点的结果。 method2和method3的数组会比字符串的长度大1
3. AC代码
public class No139 {
public boolean wordBreak(String s, List<String> wordDict) {
return method1(s, new HashSet<String>(wordDict), 0, new Boolean[s.length()]);
// return method2(s,wordDict);
// return method3(s,wordDict);
}
/**
*
*
* 执行耗时:6 ms,击败了77.33% 的Java用户
* 内存消耗:38.7 MB,击败了58.73% 的Java用户
*
* @param s 判断的字符串
* @param dirt 字典
* @param start 字符串的偏移量
* @param memo 用来记录字符串的每个偏移量往后能否被拆分的数组
* @return
*/
private boolean method1(String s, Set<String> dirt, int start, Boolean[] memo) {
// 到了最后一位
if(start == s.length()) {
return true;
}
// 如果数据字典对于该偏移量的结果已经有了,直接返回
// 这个结果数组会在处理过程中慢慢完善
if(memo[start] != null) {
return memo[start];
}
//end <= 因为subtring的末尾是不算的,要不然会少一位
for (int end= start+1;end<=s.length();end++) {
// 分为左串 和 右串,左串是符合要求的,再来判断右串是否符合要求
if(dirt.contains(s.substring(start,end)) && method1(s, dirt, end, memo)) {
return memo[start] = true;
}
}
return memo[start] = false;
}
/**
* 来自 leetCode 解法 [单词划分]:
* https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode-solution/
*
* 执行耗时:9 ms,击败了58.49% 的Java用户
* 内存消耗:38.7 MB,击败了58.73% 的Java用户
*
* @param s
* @param wordDict
* @return
*/
private boolean method2(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()];
}
/**
*
* 来自 leetCode 解法: Java 动态规划优化:最长最短单词
* https://leetcode-cn.com/problems/word-break/solution/java-dong-tai-gui-hua-you-hua-zui-chang-9fou3/
*
* 执行耗时:1 ms,击败了99.74% 的Java用户
* 内存消耗:36.8 MB,击败了93.27% 的Java用户
*
* @param s
* @param wordDict
* @return
*/
private boolean method3(String s, List<String> wordDict) {
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
int maxLen = Integer.MIN_VALUE, minLen = Integer.MAX_VALUE;
Set<String> set = new HashSet<>();
for (String word : wordDict) {
set.add(word);
maxLen = Math.max(maxLen, word.length());
minLen = Math.min(minLen, word.length());
}
for (int i = 1; i < dp.length; i++) {
int near = i - minLen, far = i - maxLen;
// 节省了循环的次数,也节约了 dp数组的空间
for (int start = near; start >= 0 && start >= far; start--) {
if (dp[start] && set.contains(s.substring(start, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
}
4. 总结
在完成题目的同时,可以考虑是否还能更上一层楼,突破自己,仔细挖掘一下题目的内容(折射现实就是需求的内容以及实现的代码),是否可以更加快速的实现功能。
也就是京东的slogan ---- 多快好省 【手动狗头】
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情