[HOT100刷题日记] 139.单词拆分
本文最后更新于35 天前,其中的信息可能已经过时,如有错误请发送邮件到2350047859@qq.com

点击这里查看原题

题目描述

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

解题方法

一维动态规划(动态规划我的痛orz)
ps:不了解一维动态规划的可以移步左程云老师的视频

思路

题目分析

对于题目给出的例子:
s = “leetcode”, wordDict = [“leet”, “code”]
对字符串leetcode从后向前遍历(其实从前往后也是一样的)
对于动态规划问题:我们可以递归的思考
1. 当子串长度为1时,如果“e”存在于字典中,那么需要判断剩下的字符串“leetcod”是否在字典中
2. 当子串长度为2时,如果“de”存在于字典中,那么需要判断剩下的字符串“leetco”是否在字典中
3. 当子串长度为3时,如果“ode”存在于字典中,那么需要判断剩下的字符串“leetc”是否在字典中
4. ……以此类推
其中一种情况为true,结果就为true,因为有一种拆分形式就行
我们定义函数f(i)表示:从s[0]开始长度为i的字是否都是由字典中的单词组成(牢记这个定义,下面看不明白的时候回来想想)
之所以用长度而不用下标是为了防止子串长度为0时出现下角标为负数的情况,改写为动态规划会出现复杂的边界判断
f(i)就表示s[0,i-1]是否由字典中的单词拼接而成,所以问题就转化为了判断s[0,i-1]是不是由字典中的单词组成的

细节

我们假设字串s[0,i-1]从s[j]处拆分,s就可以拆分为两个字符串:s[0,j-1]s[j,i-1]。只要这两部分都由字典中的单词组成,s[0,i-1]就可以成功拆分!>w<
还记得我们刚才给f(i)的定义吗?

从s[0]开始长度为i的字是否都是由字典中的单词组成。

所以s[0, j-1]是否能拆分可以由f(j)表示,而剩下的字串,也就是s[j,i-1]我们直接与字典中的单词匹配。
为了方便查找,我们直接将字典数组放入哈希表(也可以自己实现字典树哦~),找到字典中最长的单词,取它的长度max_len。我们控制s[j, i-1]的长度在[0, max_len]就可以直接在哈希表中查找!>w<如果查找到s[j,i-1]在哈希表中存在,就说明s[j, i-1]是字典中的单词( •̀ ω •́ )y!

代码实现

我们再加上记忆化搜索,一个简单的递归版本就实现了(竟然意外的的通过了( •̀ ω •́ )y):

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) 
    {
        int n = s.size();
        vector<int>dp(n+1, -1);//-1表示还没有存储过值
        int max_len = INT_MIN;
        unordered_set<string> hash;
        for(auto word : wordDict)
        {
            max_len = max(max_len, (int)word.size());
            hash.insert(word);
        }    
        return f(s, hash, n, max_len, dp);
    }
    bool f(string s, unordered_set<string>& hash, int i, int max_len, vector<int> &dp)
    {
        if(i == 0)
        {
            return true;//空串符合要求
        }
        if(dp[i] != -1)
        {
            return dp[i];//记忆化搜索
        }
        int res = false;
        //j是s2的开头 i - max_len是s2开头的最小下标 因为 s2的长度为 i - j <= max_len j最小不能小于0
        for(int j = i - 1; j >=max(i - max_len, 0); j--)
        {
            if(hash.count(s.substr(j, i - j)) && f(s, hash, j, max_len, dp))
            {
                res = true;       
            }
        }
        dp[i] = res;
        return res;
    }
};

因为不确定j在什么位置可以拆分成功,所以只能将从j到i-1的长度小于max_len的所有可能都尝试一遍

有人可能会想:既然s[j, i-1]可以直通过哈希表判断,为什么s[0,j-1]不行呢?注意一下,s[j,i-1]之所以能够通过哈希表判断,是因为我们始终控制s[i, i-1]的长度小于最大单词长度,而我们无法控制和判断s[0, j-1]的长度.

由此,我们就可以通过这个递归+记忆化搜索改写出动态规划的版本:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) 
    {
        int n = s.size();
        vector<int>dp(n+1, 0);//默认为false
        int max_len = INT_MIN;
        unordered_set<string> hash;
        for(auto word : wordDict)
        {
            max_len = max(max_len, (int)word.size());
            hash.insert(word);
        }    
        dp[0] = true;
        for(int i = 1; i <= n; i++)
        {
            for(int j = i - 1; j >=max(i - max_len, 0); j--)
            {
                if(hash.count(s.substr(j, i - j)) && dp[j])
                {
                    dp[i] = true;
                }
            }
        }
        return dp[n];
    }
};

dp[i]的定义与f(i)相同,我们将用到记忆化搜索和f(i)的部分都换成dp数组,每个dp[i-1]位置的判断都依赖于前面的dp,这样我们就将从顶到底(从复杂到简单)的递归,改写为了从底到顶(从简单到复杂)的动态规划!>w<

文章略长,如果有错误敬请指出>w<~
文章中的代码都已通过测试链接~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇