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
dp[i]:子串s[0,i-1] 是否可以被空格拆分为一个或多个在字典中出现的单词
dp[i]= dp[j]&check(j,i-1)
check(j,i-1):s[j,i-1]是否包含于字典中。
注意:先给字典通过set去重,能加快处理时间。
这是对比图:
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
HashSet<String> wordSet=new HashSet<>(wordDict);
int n=s.length();
boolean[] dp=new boolean[n+1];
dp[0]=true;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(dp[j]&&(wordSet.contains(s.substring(j,i)))){
dp[i]=true;
break;
}
}
}
return dp[n];
}
}
面试题 17.13. 恢复空格
题目
哦,不!你不小心把一个长篇文章中的空格、标点都删掉了,并且大写也弄成了小写。像句子"I reset the computer. It still didn’t boot!“已经变成了"iresetthecomputeritstilldidntboot”。在处理标点符号和大小写之前,你得先把它断成词语。当然了,你有一本厚厚的词典dictionary,不过,有些词没在词典里。假设文章用sentence表示,设计一个算法,把文章断开,要求未识别的字符最少,返回未识别的字符数。
注意:本题相对原题稍作改动,只需返回未识别的字符数
示例:
输入: dictionary = [“looked”,“just”,“like”,“her”,“brother”]
sentence =“jesslookedjustliketimherbrother”
输出: 7
解释: 断句后为"jess looked just like tim her brother",共7个未识别字符。
这道题动态规划的思想和上面一题整体是相似的。
这里要具体的分割数值,所以dp用来记录。
dp[i]:表示考虑前 i 个字符最少的未识别的字符数量,从前往后计算 dp 值。
- if 第 j(j≤i) 个到第i个字符组成的子串sentence[j−1⋯i−1](注意字符串下标从 0 开始)能在词典中找到:dp[i]=min(dp[i],dp[j−1])
- else :dp[i]=dp[i-1]+1
因此,问题转化为了怎样判断子串sentence[j−1⋯i−1]是否在字典中。
这里使用trie树优化。参考题解
trie树学习
leetcode–trie树集合
注意:因为要从第i-1个字符向前遍历trie树,因此tried树应该倒序插入。
class Solution {
public int respace(String[] dictionary, String sentence) {
//构建字典树
Trie trie=new Trie();
for(String word:dictionary){
trie.insert(word);
}
//动态规划
int n=sentence.length();
int[] dp=new int[n+1];
for(int i=1;i<=n;i++){
dp[i]=dp[i-1]+1;
for(int idx:trie.search(sentence,i-1)){
dp[i]=Math.min(dp[i],dp[idx]);
}
}
return dp[n];
}
}
class TrieNode{
boolean isWord;
TrieNode[] children=new TrieNode[26];
public TrieNode() {}
}
class Trie{
TrieNode root;
public Trie(){
root=new TrieNode();
}
//单词倒序插入字典树
public void insert(String word){
TrieNode cur=root;
for(int i=word.length()-1;i>=0;i--){
int c=word.charAt(i)-'a';
if(cur.children[c]==null){
cur.children[c]=new TrieNode();
}
cur=cur.children[c];
}
cur.isWord=true;
}
// 找到 sentence中以endPos为结尾的单词,返回这些单词的开头下标
public List<Integer> search(String sentence,int endPos){
List<Integer> indicies=new ArrayList<>();
TrieNode cur=root;
for(int i=endPos;i>=0;i--){
int c=sentence.charAt(i)-'a';
if(cur.children[c]==null){
break;
}
cur=cur.children[c];
if(cur.isWord){
indicies.add(i);
}
}
return indicies;
}
}
392. 判断子序列
题目
方法一:双指针
class Solution {
public:
bool isSubsequence(string s, string t) {
int n1=s.length(),n2=t.length();
int i=0,j=0;
while(i<n1&&j<n2){
if(s[i]==t[j]){
i++;
}
j++;
}
if(i==n1) return true;
return false;
}
};
但是如果要解决后续挑战,该方法是不可以的。
方法二:动态规划
分析方法一,对于k个要查询的字符串,大量的时间用于寻找s的下一个字符在t中的位置。那么可以通过动态规划预处理出对于每个位置,26个字母将会出现的下一个位置。
f
[
i
]
[
j
]
:
f[i][j]:
f[i][j]: 从位置
i
i
i开始,下一个字母
j
j
j出现的位置
边界采用
f
[
m
]
[
j
]
=
m
f[m][j]=m
f[m][j]=m表示。
class Solution {
public:
bool isSubsequence(string s, string t) {
int n=s.length(),m=t.length();
vector<vector<int>> f(m+1,vector<int>(26));
for(int j=0;j<26;j++){
f[m][j]=m;
}
for(int i=m-1;i>=0;i--){
for(int j=0;j<26;j++){
if((t[i]-'a')==j){
f[i][j]=i;
}else{
f[i][j]=f[i+1][j];
}
}
}
int next=0;
for(int i=0;i<n;i++){
if(f[next][s[i]-'a']==m) return false;
next=f[next][s[i]-'a']+1;
}
return true;
}
};