题意
给出一个非空字符串s
以及一个字典 - 包含多个非空字符串的列表wordDict
,判断字符串s
能否由字典中的字符串拼出来,字典中的字符串可以使用多次,字典中不包含重复的字符串。其中一个样例如下:
input: s = "applepenapple", wordDict = ["apple", "pen"]
output: true
s = apple + pen + apple
input: "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
output: false
思路
想要知道的是能否拼接成s
,不妨假设s.length=n
,如果知道了s.substr(i,n-1)
是字典中的一个word
,那么如果s.substr(0,i)
能被拼接出来,s
就能拼接出来。也就是说,可以通过动态规划的方式解决这个问题,子问题是s.substr(0,i)
能否被拼接出来。状态转移方程如下:
dp[i] = s前i个字符组成的子串能否拼接出来
dp[0] = 1
dp[i] = dp[k1] || dp[k2] || ... dp[km], s.substr(kx,i)是字典中的word
需要注意的是,dp[i]
表示的是前i个字符组成的子串是否能拼接出来,所以最终的结果是dp[n]
。另一方面,因为需要判断当前字符串是否是字典中的一个word
,所可以使用hashtable、hashset等数据结构来表示,我使用的是Trie
树来表示(刚做过一个Trie
树的题),通过预处理得到了s中以i结尾的子串是一个word的起始位置
,具体方式是遍历s
并在Trie
树上搜索,不过并没有哈希表快。
代码
typedef vector<int> VI;
inline int char2int(char ch) {
int res = ch - 'A';
return res >= 26 ? res -= 6 : res;
};
struct Node {
int val = 0;
Node *t[52] = {};
Node(){};
};
class Trie {
public:
Node *root;
Trie() { root = new Node(); }
void insert(string word) {
const int n = word.size();
if (n <= 0)
return;
int chi = char2int(word[0]);
Node *p = root->t[chi] == nullptr ? (root->t[chi] = new Node()) : root->t[chi];
for (int i = 0; i < n - 1; i++) {
p->val == -1 ? p->val = 0 : 0;
chi = char2int(word[i + 1]);
p = p->t[chi] == nullptr ? p->t[chi] = new Node() : p->t[chi];
}
p->val == -1 ? p->val = 1 : p->val += 1;
}
};
class Solution {
public:
bool wordBreak(string s, vector<string> &wordDict) {
const int n = s.size();
if (wordDict.size() <= 0)
return false;
Trie *tree = new Trie();
for (string tmps : wordDict)
tree->insert(tmps);
vector<int> sofdp[n + 1];
for (int i = 0; i < n + 1; i++)
sofdp[i] = vector<int>();
for (int i = 0; i < n; i++)
update(s, i, sofdp, tree);
bool dp[n + 1] = {1};
for (int i = 1; i <= n; i++) {
for (int v : sofdp[i - 1]) {
dp[i] = dp[i] || dp[v];
if (dp[i] == 1)
break;
}
}
return dp[n] == 1;
}
void update(string &s, int start, vector<int> sofdp[], Trie *tree) {
const int n = s.size();
if (start >= n)
return;
Node *p = tree->root;
for (int i = start; i < n; i++) {
int chi = char2int(s[i]);
if (!p || !(p->t[chi]) || p->t[chi]->val == -1)
break;
if (p->t[chi]->val == 1)
sofdp[i].push_back(start);
p = p->t[chi];
}
}
};
总结
动态规划的思路不难想到,但是需要采用适当的数据结构优化。采用Trie树的方式空间复杂度较大,时间复杂度大约为O( n 2 n^2 n2)。