[Leetcode] Word Break 单词分解

Word Break I

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^2) 空间 O(N)

思路

如果一个单词存在一种分解方法,分解后每一块都在字典中,那必定满足这么一个条件:对于该单词的最后一个分割点,这个分割点到单词末尾所组成的字符串是一个单词,而这个分割点到单词开头所组成的字符串也是可分解的。所以只要验证满足这个条件,我们则可以确定这个较长的字符串也是可分解的。

finishleetcode
finishleetco de
true         false
finishleet code
false      true
finish leetcode
true   true

所以我们用外层循环来控制待验证的字符串的长度,而用内层的循环来寻找这么一个分割点,可以把字符串分成一个单词和一个同样可分解的子字符串。同时,我们用数组记录下字符串长度递增时可分解的情况,以供之后使用,避免重复计算。

代码

public class Solution {
    public boolean wordBreak(String s, Set<String> wordDict) {
        boolean[] dp = new boolean[s.length()+1];
        Arrays.fill(dp,false);
        dp[s.length()]=true;
        // 外层循环递增长度
        for(int i = s.length()-1; i >=0 ; i--){
            // 内层循环寻找分割点
            for(int j = i; j < s.length(); j++){
                String sub = s.substring(i,j+1);
                if(wordDict.contains(sub) && dp[j+1]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[0];
    }
}

Word Break II

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”].

动态规划

复杂度

时间 O(N^2) 空间 O(N^2)

思路

我们从第一个字母开始,遍历字典,看从第一个字母开始能组成哪个在字典里的词,如果找到一个,就在这个词的结束位置下一个字母处,建立一个列表,记录下这个词(保存到一个列表的数组)。当遍历完这个词典并找出所有以第一个字母开头的词以后,我们进入下一轮搜索。下一轮搜索只在之前找到的词的后面位置开始,如果这个位置不是一个词的下一个字母位置,我们就跳过。这样我们相当于构建了一个树(实际上是列表数组),每个找到的词都是这个树的一个分支。有了这个“树”,然后我们再用深度优先搜索,把路径加到结果当中就行了。

c
a
t                *              *
s -- cat         |              |
a -- cats        cats          cats
n                 \            /
d                  \          /
d -- and, sand      and    sand
o                    \      /
g                     \    /
  -- dog                dog
  

注意

在backtracking的时候不用考虑下标超界(小于0)的情况,直接将所有到0的都加入结果就行了,因为我们在建这个路径时,就是从0开始建的,不可能超界。

代码

public class Solution {
    
    public List<String> res = new LinkedList<String>();
    
    public List<String> wordBreak(String s, Set<String> wordDict) {
        List<String> dp[] = new ArrayList[s.length()+1];
        dp[0] = new ArrayList<String>();
        for(int i = 0; i < s.length(); i++){
            // 只在单词的后一个字母开始寻找,否则跳过
            if(dp[i]==null) continue;
            // 看从当前字母开始能组成哪个在字典里的词
            for(String word : wordDict){
                int len = word.length();
                if(i + len > s.length()) continue;
                String sub = s.substring(i, i+len);
                if(word.equals(sub)){
                    if(dp[i + len] == null){
                        dp[i + len] = new ArrayList<String>();
                    }
                    dp[i + len].add(word);
                }
            }
        }
        // 如果数组末尾不存在单词,说明找不到分解方法
        if(dp[s.length()]!=null) backTrack(dp, s.length(), new ArrayList<String>());
        return res;
    }
    
    private void backTrack(List<String> dp[], int end, ArrayList<String> tmp){
        if(end <= 0){
            String path = tmp.get(tmp.size()-1);
            for(int i = tmp.size() - 2; i >= 0; i--){
                path += " " + tmp.get(i);
            }
            res.add(path);
            return;
        }
        for(String word : dp[end]){
            tmp.add(word);
            backTrack(dp, end - word.length(), tmp);
            tmp.remove(tmp.size()-1);
        }
    }
}
    原文作者:ethannnli
    原文地址: https://segmentfault.com/a/1190000003698693
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞