Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode"
,
dict = ["leet", "code"]
.
Return true because "leetcode"
can be segmented as "leet code"
.
之前好像是写过这个题目的解题报告,说实话这个题目我实在是理解了好久。今天花了一整天时间研究这个题目,最终自己给出了一个O(n^3)的dp算法,然后在Discuss中看了各路大神们的算法,我真是太。。。
话不多说,正题:
思路1:dfs
深度优先搜素,对于字符串s[0,..,n]每次判断其前缀是否可以按照字典所给出的单词进行分词操作,递归进行,分词之字符串尾时返回,代码如下:
//递归求解每次检查当前字串前缀,如果可分则继续深度查询 class Solution { public: bool wordBreak(string s, unordered_set<string> &dict) { issegment=false; wordBreak(s,0,dict); return issegment; } void wordBreak(const string &s,int pos,unordered_set <string> &dict){ if(pos==s.size()) issegment=true; if(issegment) return; for(int i=pos;i<s.size();i++){ auto iter=dict.find(s.substr(pos,i-pos+1)); if(iter!=dict.end()){ wordBreak(s,i+1,dict); } } } private: bool issegment; };
思路2:dp
我自己的想法是,对于字符串s[i,..,j]是否可以按照字典所给单词进行分词,存在以下两种情况:
1) s[i,..,j]在字典dict中;
2) 至少存在一个k(i<k<j)使得s[i,..,k]和s[k+1,..,j]都可以被分词;
为此使dp[i][j]表示s[i,..,j]是否可以被分词,递归式如下:
dp[i][j] = true if dictContain(s[i,..,j]) or (dictContain(s[i,..,k])&&dictContain(s[k+1,..,j]))
= false other
由此,有点类似于矩阵连乘问题,共有O(n^2)个子问题,每个子问题有包括O(n)种选择,算法时间复杂度O(n^3),空间复杂度O(n^2)。
代码如下:
#include <string> #include <vector> using namespace std; class Solution{ public: bool wordBreak(string s,unordered_set <string> &dict){ int len=s.size(); if(!len){ return false; } vector <vector <bool> > dp(len,vector <bool>(len,0)); //initialize dp for(int i=0;i<len;i++){ dp[i][i]=dictContain(s.substr(i,1),dict); } //dp for(int l=2;l<=len;l++){ for(int beg=0;beg+l<=len;beg++){ dp[beg][beg+l-1]=dictContain(s.substr(beg,l),dict); if(dp[beg][beg+l-1]){continue;} for(int k=1;k<l;k++){ dp[beg][beg+l-1]=dp[beg][beg+k-1]&&dp[beg+k][beg+l-1]; if(dp[beg][beg+l-1]) break; } } } return dp[0][len-1]; } bool dictContain(const string &s,const unordered_set <string> &dict){ auto iter=dict.find(s); if(auto==dict.end()){ return false; } return true; } }
看了一位大神的代码,总结一下,发现其实换个角度,我们只需要考虑s[0,..,i]是否可以被分词,s[0,..,j]可以被分词的充分必要条件是:
1)存在一个k(0<k<i)使得s[0,..,k]可以被分词并且s.substr(k+1,j)是字典dict中的单词 OR
2)s[0,..,j]本身就在字典dict中;
由此每个子问题的子问题变为只有1个,同时对于子问题的选择有O(n)种,为此使用数组
dp[0,s.size()]记录分词信息,其中dp[i]表示s[0,..,i]是否可以被分词,
dp[i] = true if dictContain(s[0,..,i]) or ((dictContain(s[k+1,..,j]))&&dp[k])
最终结果为dp[s.size()-1],时间复杂度O(n^2),空间O(n),代码如下:
class Solution{ public: bool wordBreak(string s,unordered_set <string> &dict){ int len=s.size(); vector <bool> dp(len+1,0); dp[0]=true; for(int i=1;i<=len;i++){ //check if s.substr(0,i) can be broken string str=s.substr(0,i); for(int j=0;j<i;j++){ if(dp[j]&&dictContain(str,dict)){ dp[i]=true; break; } str.erase(str.begin()); } } return dp[len]; } bool dictContain(string s,const unordered_set <string> &dict){ auto iter=dict.find(s); if(iter!=dict.end()){ return true; } return false; } } ;