leetcode 链接:139. 单词拆分

题目

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明

  • 拆分时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词。

示例:

  1. 输入: s = "leetcode", wordDict = ["leet", "code"]
  2. 输出: true
  3. 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重复使用字典中的单词。
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

解答 & 代码

先将字典中的单词存入哈希表 wordMap,提高查找效率
动态规划

  • 设置数组 dpdp[i] 代表字符串前 i 个字符(即 s[0...i-1])能否被拆分成字典中的单词
  • 初始化 dp[0] = true,即长度为 0 的空字符串可以被拆分
  • i = 1 开始直到 i = len,求 dp[i]

    • 遍历每个切分点 j ∈ [0, i),状态转移方程为 [中等] 139. 单词拆分 - 图1

      class Solution {
      public:
      bool wordBreak(string s, vector<string>& wordDict) {
         // 将字典中的单词存入哈希表,提高查找效率
         unordered_set<string> wordMap;
         for(int i = 0; i < wordDict.size(); ++i)
             wordMap.insert(wordDict[i]);
      
         // 动态规划
         int len = s.size();
         vector<bool> dp(len + 1, false);    // dp[i] 代表字符串前 i 个字符(即 s[0...i-1])能否被拆分成字典中的单词
         dp[0] = true;
         for(int i = 1; i <= len; ++i)
         {
             for(int j = 0; j < i; ++j)
             {
                 if(dp[j] && wordMap.find(s.substr(j, i - j)) != wordMap.end())
                 {
                     dp[i] = true;
                     break;
                 }
             }
         }
      
         return dp[len];
      }
      };
      

      执行结果: ``` 执行结果:通过

执行用时:20 ms, 在所有 C++ 提交中击败了 52.28% 的用户 内存消耗:12.7 MB, 在所有 C++ 提交中击败了 52.28% 的用户

对上述方法进行**剪枝**:<br />因为在枚举切分点 `j` 时,要在哈希表中查询 `s[j...i-1]` 是否存在,如果 `i - j` 的长度大于哈希表中最大单词长度 `maxWordLen`,那么肯定不存在,这些情况可以忽略。也就是说,遍历切分点 `j` 时,让 `j` 从 `max(0, i - maxWordLen)` 开始遍历
```cpp
class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        // 将字典中的单词存入哈希表,提高查找效率
        unordered_set<string> wordMap;
        int maxWordLen = 0;        // 单词表中最大单词长度
        for(int i = 0; i < wordDict.size(); ++i)
        {
            wordMap.insert(wordDict[i]);
            maxWordLen = wordDict[i].size() > maxWordLen ? wordDict[i].size() : maxWordLen;
        }           

        // 动态规划
        int len = s.size();
        vector<bool> dp(len + 1, false);    // dp[i] 代表字符串前 i 个字符(即 s[0...i-1])能否被拆分成字典中的单词
        dp[0] = true;
        for(int i = 1; i <= len; ++i)
        {
            // 剪枝:遍历切分点 j 时,让 j 从 max(0, i - maxWordLen) 开始遍历
            int j = (i - maxWordLen >= 0) ? i - maxWordLen : 0;    
            for(; j < i; ++j)
            {
                if(dp[j] && wordMap.find(s.substr(j, i - j)) != wordMap.end())
                {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[len];
    }
};

执行结果:

执行结果:通过

执行用时:4 ms, 在所有 C++ 提交中击败了 93.33% 的用户
内存消耗:7.5 MB, 在所有 C++ 提交中击败了 75.50% 的用户