LeetCode 139 Word Break 动态规划

LeetCode 139 Word Break

题意

给出一个非空字符串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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值