码迷,mamicode.com
首页 > 编程语言 > 详细

[C++]LeetCode: 113 Word Break II (DP && Backtacking) 求解拆分组合

时间:2015-01-20 22:22:42      阅读:241      评论:0      收藏:0      [点我收藏+]

标签:dynamic programming   backtracking   leetcode   

题目:

Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.

Return all such possible sentences.

For example, given
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].

A solution is ["cats and dog", "cat sand dog"].

Answer 1: BF递归解 TLE

思路:每次维护一个当前结果集,然后遍历剩下的所有子串,如果子串在字典内出现,则保存一下结果。并放入下一层递归剩下的所有字符。

不过这种做法在Leetcode中会超时。

Error Code:

class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string> &dict) {
        vector<string> ret;
        if(s.size() == 0) return ret;
        wordBreak_helper(s, dict, 0, "", ret);
        return ret;
    }
    
private:
    void wordBreak_helper(string s, unordered_set<string>& dict, int pos, string tmp, vector<string>& ret)
    {
        if(pos == s.size())
        {
            ret.push_back(tmp.substr(0, tmp.size()-1));  //去除空格
            return;
        }
        
        for(int i = pos; i < s.size(); i++)
        {
            for(int j = 1; j < s.size()-i; j++)
            {
                string substr = s.substr(pos, j);
                if(dict.find(substr) != dict.end())
                {
                    wordBreak_helper(s, dict, i+1, tmp + " " + substr, ret);
                }
            }
        }
    }
};

Answer 2: 加入DP

思路:这道题,我们借鉴Word Break中的动态规划思路。不过这次我们需要记录每一个拆分结果,所以使用二维数组保存动态规划结果,二维数组形式vector<list<int>> dp, dp[i]表示字符串从坐标 开始的所有合法单词的终止位置列表。我们从字符串末尾开始,自底向上去求解动态规划数组,我们以当前stop为终止位置,循环所有合法的开始位置,如果子串在字典内,相应的在dp数组内添加元素。需要注意的是,如果从当前stop开始并无匹配的字典内单词(说明此处不可断),说明可以跳过这个stop做截止位置.

求解完所有可能的组合后,我们通过回溯法,去求解所有可能的分割情况。这次自顶向下求,从字符串头开始,遍历所有结果,直到到达字符串末尾。

Attention:

1. stop的含义,表示单词结束坐标的后一位,即下一个单词的首位或字符串length。所以stop从s.size()开始。

for(int stop = s.size(); stop >= 0; stop--)
2. 如果从当前stop开始并无任何字典内单词,可以跳过这个断点。

 //如果从stop开始并无匹配的单词,可以跳过审查这个stop坐标的截止单词(stop表示单词的最后一个字母的后一位)
            if(stop < s.size() && mark[stop].empty()) continue;
3. 从当前stop前一位开始,循环所有合法的开始位置,并更新动态规划数组。

/检查以当前stop截止,之前的所有在词典内的单词,并添加进mark中。
            for(int start = stop - 1; start >= 0; start--)
            {
                string substr = s.substr(start, stop - start);
                if(dict.find(substr) != dict.end())
                {
                    mark[start].push_back(stop);
                }
            }
4. C++11的for的新用法。

for述句将允许简单的范围迭代:

int my_array[5] = {1, 2, 3, 4, 5};
for (int &x : my_array)
{
  x *= 2;
}

for述句的第一部份定义被用来做范围迭代的参数,就像被声明在一般for循环的参数一样,其作用域仅只于循环的范围。而在":"之后的第二区块,代表将被迭代的范围。 并且,循环变量是一个引用,可以在循环中改变它的值。

for(int& stop : mark[index])

程序中,这一句,就表示循环所有的以index开始的单词的stop位置。

5.回溯时,我们注意要维护当前的path,不能用添加子串的新串去替代path,因为这是个循环语句,我们下一个stop依然要使用原本的path(未添加子串).

for(int& stop : mark[index])
        {
            string substr = s.substr(index, stop - index);
            string newpath = path + (index == 0 ? substr : " " + substr);
AC Code:

class Solution {
public:
    vector<string> wordBreak(string s, unordered_set<string> &dict) {
        //mark[i]存储了从i坐标开始,在字典内的单词的所有截止坐标。
        vector<list<int>> mark(s.size(), list<int>());
        
        //从字符串末尾至头
        for(int stop = s.size(); stop >= 0; stop--)
        {
            //如果从stop开始并无匹配的单词,可以跳过审查这个stop坐标的截止单词(stop表示单词的最后一个字母的后一位)
            if(stop < s.size() && mark[stop].empty()) continue;
            //检查以当前stop截止,之前的所有在词典内的单词,并添加进mark中。
            for(int start = stop - 1; start >= 0; start--)
            {
                string substr = s.substr(start, stop - start);
                if(dict.find(substr) != dict.end())
                {
                    mark[start].push_back(stop);
                }
            }
        }
        
        vector<string> ret;
        generate(mark, 0, s, "", ret);
        return ret;
    }

private:
    void generate(vector<list<int>> mark, int index, const string& s, string path, vector<string>& ret)
    {
        for(int& stop : mark[index])
        {
            string substr = s.substr(index, stop - index);
            string newpath = path + (index == 0 ? substr : " " + substr);
            if(stop == s.size())
                ret.push_back(newpath);
            else
                generate(mark, stop, s, newpath, ret);
        }
    }
};

这道题综合性很强,将DP和Backtracking结合起来,难度较大,尤其是建立dp数组时的方法可以重点借鉴。




[C++]LeetCode: 113 Word Break II (DP && Backtacking) 求解拆分组合

标签:dynamic programming   backtracking   leetcode   

原文地址:http://blog.csdn.net/cinderella_niu/article/details/42922329

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!