[LeetCode]Word Break

Posted 乘风有时

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[LeetCode]Word Break相关的知识,希望对你有一定的参考价值。

题目:Word Break

将给定的字符串按照给定的单词表拆分开,判断是否能在给定的单词表中拆分该字符串

单词表中单词不重复,但是可以在字符串中重复出现。

思路:

暴力搜索,只要遍历单词表每次找到与字符串的当前位置往后的n(单词表中当前遍历到的单词长度)的字符重合则表示可以拆分。

这种方法,时间复杂度很高,无法通过。后面还会介绍,代码先略过。

 

思路:BFS(广度优先搜索)

每次搜索能与当前位置后面n(n是匹配的单词的长度,是可变的)个字符匹配的所有单词,加入队列中,然后修改当前的位置,同时,当前的元素出队;

知道找一个单词使得当前位置修改后到达给定字符串的尾部。

例如:

s = "leetcode" worDict = {"leet","code"} pos = 0->n(8);

初始状态,当pos = 0时,leet与[0,4]的字符匹配,每次需要知道之后pos开始的位置和当前匹配的单词在单词表中的位置,因此我用pair记录上面两个值,将他们入队;

Q = {<0,4>}

然后单词表中没有其他单词匹配,修改当前位置pos = Q.front().second;

修改后的pos < s.size();则继续匹配单词表,如此循环;

Q = {<0,4>,<1,8>},此时,找到一个组合,由于只需要判断是否有这样的组合,所有直接返回。

/**广度优先搜索**/
bool LeetCode::wordBreakBFS(string s, vector<string>& wordDict){
    if (!wordDict.size())return false;
    queue<pair<int, int>>Q;//first是单词在词典中的下标,second是当前单词之后开始的位置
    auto it = wordDict.cbegin();
    while (it != wordDict.cend()){
        if ((*it).size() <= s.size() && !s.substr(0, (*it).size()).compare(*it)){//字符串与s的开始部分匹配,找到开始的字符串
            if ((*it).size() == s.size())return true;//和一个单词完全匹配
            Q.push(make_pair(it - wordDict.cbegin(),(*it).size()));
        }
        ++it;
    }
    while (!Q.empty()){
        auto p = Q.front();
        Q.pop();
        auto it = wordDict.cbegin();
        while (it != wordDict.cend()){//遍历完单词表,找到所有的可能
            if ((*it).size() + p.second <= s.size() && !s.substr(p.second, (*it).size()).compare(*it)){//字符串与s的从p.second开始部分匹配
                if (p.second + (*it).size() == s.size())return true;//匹配完毕
                Q.push(make_pair(it - wordDict.cbegin(), p.second + (*it).size()));
            }
            ++it;
        }
    }
    return false;
}

但是,上面的方法还是不能通过,超时了,因为给的是vector在找匹配单词上耗时与爆搜是一样的,而且,没有办法减少重复的搜索,所以效率和爆搜差不多,可能是我没有想到什么好的办法减少重复搜索,请知道的人指导一下,总之我没有用这个思路通过。

 

思路:

考虑到不需要找到具体的路劲,我在想动态规划是不是可以呢?

其实是可以的,F(n)表示s字符串中0到n个字符是否能用单词表的单词表示;

F(n) = F(k) && (s.substr(k,n) in wordDict);存在 k in [0 - n]

上面表达式的意思:存在这样的k属于[0,n)使得 F(n) = F(k) 且 (s中k到n的子串在单词表中能找到单词与它匹配)

所以,可以从零开始找到字符串每个位置的对应的F(k)值,然后,返回F(s.size())即可。

注意:

vector<bool>flag表示的F(),它的大小应该是s.size()+1;

每次求end对应位置的F(end)值,需要遍历[0,end)的所有值中是否又满足上面的k(start)

/**
动态规划:F(n) = F(k) && (s.substr(k,n) in wordDict);存在 k in [0 - n]
**/
bool LeetCode::wordBreakBF(string s, vector<string>& wordDict){
    if (!wordDict.size())return false;
    sort(wordDict.begin(), wordDict.end());
    vector<bool> flag(s.size() + 1,false);//标记每个地方对应的字符串是否能被拼凑出来
    flag.at(0) = true;
    for (int end = 1; end <= s.size(); end++){
        for (int start = end - 1; start >= 0; start--){//检查是否存在上面的k,拼凑出[0,end]的字符串
            if (flag.at(start)){
                string temp = s.substr(start,end - start);//获得从start到end的子串
                auto it = wordDict.begin();
                while (it != wordDict.end()){//遍历单词表
                    auto r = temp.compare(*it);
                    if (!r){
                        flag.at(end) = true;
                        break;
                    }
                    else if(r < 0){//单词表已排序
                        break;
                    }
                    ++it;
                }
            }
        }
    }
    return flag.at(s.size());
}

题目:Word BreakII

在上面的基础上要找出所有可能的不同组合。

单词表中单词不重复,但是可以在字符串中重复出现。

基本上上面的3种方法都可以在这个问题上使用;

思路:

暴力搜索:

定义一个数组记录搜索的路径,同时找到方便回溯时的开始位置,暴力搜索和深度优先很相似;

先找一个可能匹配整个字符串的数组,然后一直匹配到最后,如果不能完全匹配就回溯,此时,要从记录的path的值为起始遍历整个字典。

/**
暴力搜索
Time Limit Exceeded
**/
vector<string> LeetCode::wordBreak2(string s, vector<string>& wordDict){
    vector<string> retStrs;
    if (!wordDict.size())return retStrs;
    int i = 0, pos = 0;
    vector<int> path;
    while (pos < s.size()){
        for (; i < wordDict.size(); i++){//找到匹配的单词
            string& temp = wordDict.at(i);
            //单词长度不超过剩下的未匹配长度
            if (pos + temp.size() <= s.size() && !s.compare(pos, temp.size(), temp)){//temp.compare(s.substr(pos,temp.size()))
                pos += temp.size();
                path.push_back(i);
                i = 0;
                break;
            }
        }
        if (pos == s.size()){//s完全匹配成功
            auto it = path.cbegin();
            string s;
            while (it != path.cend()){
                s.append(wordDict.at(*it));
                ++it;
                if(it != path.cend())s.append(" ");
            }
            retStrs.push_back(s);
            i = wordDict.size();
        }
        if (i == wordDict.size()){//匹配不成功,回溯
            if (!path.size())break;//回溯到开始时,还是没有匹配,则跳出循环
            i = path.at(path.size() - 1);//不能用back(),他返回的是引用
            pos -= wordDict.at(i).size();
            path.pop_back();
            ++i;
        }
    }
    return retStrs;
}

结果超时了。在和前面第一个问题差不多的一个测试用例的地方超时;

思路:BSF

过程和前面差不多,只是,在判断成功的地方合并字符串,而不是返回;同时为了能找回路,需要有辅助数组来记住队列弹出的元素。

同时还要记住后面的元素是因为前面的哪个元素而入队的,即入队的路径。这里不再贴代码。

同样要面对暴力搜索时的超时测试用例。

 

思路:动态规划

直接在上面动态规划的思想上来修改,即是,在搜索的时候记录下匹配的路径。

我的做法是:定义一个record数组vector<vector<pair<int,int>>>数组的一维的大小和flag相同,他分别对应flag的每个值,

F(n) = F(k) && (s.substr(k,n) in wordDict);存在 k in [0 - n];k可能有多个值;

record的二维的大小是该位置pos对应的s的子串s.substr(0,pos)匹配的多个组合中的k的值和子串对应的单词表的位置;

first记录flag为true时的单词表的下标,second记录flag为true时的上一个依赖的位置。

于是可通过下面的语句记录路径

record.at(end).push_back(make_pair(it - wordDict.begin(),start));

然后getAllPathFromWB()函数来得到最终的字符串的组合。

该函数getAllPathFromWB()中定义visited数组记录record二维数组中第二维的起始位置,以便回溯的时候从前面的位置接着向后面遍历。

注意:

1.循环是从record的尾部即字符串s的尾部开始的,且该位置没有对应visited的值,因为,它是外循环,不需要记录上一次的遍历的位置。

2.找到一个完整的组合后,删除栈顶元素,并将visited数组的尾部元素加一

3.整个getAllPathFromWB()的思想类似于深度遍历。

void LeetCode::getAllPathFromWB(vector<string>& wordDict, vector<string>& strs, vector<vector<pair<int, int>>>& record){
    stack<pair<int,int>>s;
    vector<int>visited;
    int i = record.size() - 1;
    auto it = record.at(i).cbegin();
    while (it != record.at(i).cend()){//record的最后元素开始根据记录的下标回溯
        s.push((*it));
        while (!s.empty()){
            auto p = s.top();
            if (!p.second){//一条完整路径
                vector<pair<int,int>>temp;
                string str;
                while (!s.empty()){//出栈并组合单词为句子
                    auto t = s.top();
                    s.pop();
                    str.append(wordDict.at(t.first));
                    temp.push_back(t);
                    if (!s.empty())str.append(" ");
                }
                strs.push_back(str);
                for (int j = temp.size() - 1; j > 0 ; --j){//入栈,但是去掉栈顶元素(j > 0)
                    s.push(temp.at(j));
                }
                if (visited.size() > 0)++visited.back();//记录最后一个单词的上一个位置的数组加一
            }
            else{
                int j = 0;
                if (visited.size() && visited.size() == s.size()){//visited数组已经存在
                    j = visited.back();
                    if (j == record.at(p.second).size()){//record的当前数组是否遍历完
                        visited.pop_back();
                        if(visited.size() > 0)++visited.back();//可能回溯到最上面一个句子
                        s.pop();
                        continue;
                    }
                    s.push(record.at(p.second).at(j));
                }
                else{//visited数组不存在
                    s.push(record.at(p.second).at(j));
                    visited.push_back(j);
                }
            }
        }
        ++it;
    }
}

/**
动态规划:F(n) = F(k) & s.substr(k,n) in wordDict;存在 k in [0 - n]
**/
vector<string> LeetCode::wordBreak2BF(string s, vector<string>& wordDict){
    vector<string> retStrs;
    if (!wordDict.size())return retStrs;
    sort(wordDict.begin(), wordDict.end());
    vector<bool> flag(s.size() + 1, false);//标记每个地方对应的字符串是否能被拼凑出来
    vector<vector<pair<int,int>>> record(s.size() + 1);//first记录flag为true时的单词表的下标,second记录flag为true时的上一个依赖的位置
    flag.at(0) = true;
    for (int end = 1; end <= s.size(); end++){
        for (int start = end - 1; start >= 0; start--){//检查是否存在上面的k,拼凑出[0,end]的字符串
            if (flag.at(start)){
                string temp = s.substr(start, end - start);//获得从start到end的子串
                auto it = wordDict.begin();
                while (it != wordDict.end()){//遍历单词表
                    auto r = temp.compare(*it);
                    if (!r){
                        flag.at(end) = true;
                        record.at(end).push_back(make_pair(it - wordDict.begin(),start));
                        break;
                    }
                    else if (r < 0){//单词表已排序
                        break;
                    }
                    ++it;
                }
            }
        }
    }
    if (flag.at(s.size())){
        getAllPathFromWB(wordDict,retStrs,record);
    }
    return retStrs;
}

 

以上是关于[LeetCode]Word Break的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 139. Word Break

[LeetCode] 139 Word Break

leetcode笔记:Word Break

LeetCode - Word Break

LeetCode #139. Word Break C#

[Leetcode] word break 拆分词语