中级农民
- 积分
- 138
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2016-9-7
- 最后登录
- 1970-1-1
|
第二面:这个题等价于求string s中的最长subsequence found in dictionary(注意是subsequence,不是substring)。其实可对string s用DP。DP的一个维度就是从某 index=j 开始的子串s.subtr(j)对j = N, j>=0, j-- DP. 另一个维度是字典的prefix,每次匹配了一个字母时就只看字典中以该字母为首的单词,所以要用Trie建立字典。那么DP状态转移方程就容易写了:定义dp[j][TrieNode* x] := s.substr(j)的最长的能在Trie字典 x 找到的subsequence长度,那么:
dp[j][TrieNode* x] = max(dp[j+1][x], dp[j+1][x->next[s[j]-'a']]+1 or 0);
就是说在寻找s.substr(j)在字典 x的subsequence时,若不匹配char s[j],那么就是在寻找s.substr(j+1)在字典 x的最长subsequence;否则就是匹配s[j],然后在寻找s.substr(j+1)在字典 x->next[s[j]-'a']的最长subsequence,然后再根据结果和Trie node x->next[s[j]-'a']本身是否代表一个单词来决定+1或不加。最后要求的答案就是dp[0][root], root为原始字典的TrieNode root.
时间复杂度为O(Ns*Nt),Ns=s.length(), Nt=TrieNode个数(这个比单词长度总和是可以远远得小的,因为合并了相同的prefix)。所以比用暴力比较s和每个单词的时间O(Ns*Nd)比还是快很多的 (Nd=字典单词长度总和)
- struct Node { // Trie node for dictionary
- int isWord; // 1 if representing a word; otherwise 0
- vector<Node*> next;
- Node():isWord(0),next(vector<Node*>(26)) {}
- };
- Node* createTrie(const vector<string>& dict) {
- auto r = new Node(), cur = r;
- for (const auto& s:dict) {
- for (char c:s) {
- int i = c - 'a';
- if (!cur->next[i]) cur->next[i] = new Node();
- cur = cur->next[i];
- }
- cur->isWord = 1;
- }
- return r;
- }
- void postOrder(Node* r, vector<Node*>& post) {
- if (!r) return;
- for (auto x:r->next) postOrder(x, post);
- post.push_back(r);
- }
- int longestSubsequence(string& s, const vector<string>& dict) {
- int len = s.length(); if (!len) return 0;
- auto r = createTrie(dict);
- vector<Node*> post; postOrder(r, post);
- // dp[i][x] = length of longest subsequence of s.substr(i)
- // found in dictionary defined by trie node x
- auto dp = vector<unordered_map<Node*, int>>(len+1); // default all 0's
- for (int i = len - 1; i >= 0; --i) {
- for (auto x:post) {
- auto y = x->next[s[i] - 'a'];
- if (y) dp[i][x] = dp[i+1][y]? (dp[i+1][y]+1) : y->isWord;
- dp[i][x] = max(dp[i][x], dp[i+1][x]);
- }
- }
- return dp[0][r];
- }
复制代码 |
|