LeetCode | 139. 单词拆分

原题Medium): 

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

  说明:

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

  

 

 

 

思路:动态规划

  这题有个陷阱,需要特别注意,就是非空字符串s的字母是可以被重复使用的,例如 s = "cars", wordDict = ["car", "ca", "rs"],输出是true,cars可以被分为car和rs,重复使用了字母r。就是因为这点这题的难度就大了很多。至少不能用类似双指针的暴力解法了。

  因为这点,说明字符串s的任何长度的子串都能作为一个单词,亦如"cars",它的组合有c、ca、car、cars、a、ar、ars、r、rs、s。我们可以用一数组记录以任意长度子串能否拆分为单词的可能,即一bool数组dp,而dp[i]代表字符串s从s[0]开始到s[i]这一个子串能否拆分为单词的可能,而dp[i]是否成立(true or false),取决于dp[j]是否为(true)和s[j+1]到s[i]组成的子串能否在字典中找到对应的单词,这段话可能有点难以理解,可以配合图:

 

   dp[j]如果为true,说明从s[0]开始到s[j]的这一个子串能被拆分为单词,那么此时dp[i]要想成立,只需看能否在字典中找到与s[j+1]到s[i]组成的子串匹配的单词了,找到就dp[i]为true,找不到就j++,直到j等于i为止。如果dp[j]为false,那么即使s[j+1]到s[i]组成的子串能在字典中被找到也无济于事,所以如果dp[j]为false,j直接++。那么j从0开始,直到i为止。这样s[0]开始到s[i]这一个子串能否拆分为单词的所有可能性都被我们尝试过了(如果中途有成立的情况就可以跳出了)。那么i从0开始到整个字符串末尾结束,整个字符串能否被拆分为单词的所有可能性都被我们尝试过了。

  不过超出字典最长单词长度的距离是可以忽略的,例如字典最长单词长度为4,那么j和i的距离只要大过4就没有意义,如果i已经大于4了,j就可以直接从i-4开始。所以我们需要先获取字典最长单词的长度。

 1 bool wordBreak(string s, vector<string>& wordDict) {
 2     //dp数组下标从1开始,有利于计算子串
 3     vector<bool> dp(s.size() + 1, false);
 4     dp[0] = true;
 5     int maxLength = 0;
 6     auto beg = wordDict.begin();
 7     auto end = wordDict.end();
 8     //获取字典最长单词长度
 9     for (int i = 0; i < wordDict.size(); i++)
10         if (maxLength < wordDict[i].length())    
11             maxLength = wordDict[i].length();
12     
13     //在前maxLength个字符组成的字符串时,j都从0开始,如果i已经大于maxLength了,j就从i - maxLength开始
14     for (int i = 1; i <= s.length(); i++)
15         for (int j = std::max(i - maxLength, 0); j < i; j++)
16             //取决于dp[j]是否为(true)和s[j+1]到s[i]组成的子串能否在字典中找到对应的单词
17             if (dp[j] && find(beg, end, s.substr(j, i - j)) != wordDict.end())
18             {
19                 //substr的作用是从字符串s的j号元素出发(从0开始算的j,但不包括j),之后的i - j个元素形成一个子串返回。
20                 dp[i] = true;
21                 break;
22             }
23     return dp[s.size()];
24 }

posted @ 2019-10-25 17:11  羽园原华  阅读(173)  评论(0编辑  收藏  举报