LeetCode_Word Break

来源:互联网 发布:js单选按钮选中的值 编辑:程序博客网 时间:2024/05/18 00:55

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代码的简洁美


0 0
原创粉丝点击