LeetCode_Word Break

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;
};

dfs有太多的重复计算,具体图例在我第一遍作的时候就已经画图了,因此想到dp:

思路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;
  }
}

思路3:dp+dfs

看了一位大神的代码,总结一下,发现其实换个角度,我们只需要考虑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;
  }
} ;

最后说一句忘了在哪个文章中看到的一句话:dp真是一个神奇的东西,只有你自己写了才能体会到,dp代码的简洁美


你可能感兴趣的:(LeetCode)