Sequence DP - 139. Word Break && 140. Word Break ii

139: https://leetcode.com/problems/word-break/description/
140: https://leetcode.com/problems/word-break-ii/description/

139. Word Break

// 状态定义:boolean dp[i] 为 s.substring(0, i) 是否 breakable

// 状态转移方程:
// dp[i] = true if any index j < i, dp[j] is true && s.substring(j, i) in dictionary

// 初始化:boolean[] dp = new boolean[len + 1]; dp[0] = true
// 循环体
// 返回 target dp[len]

public boolean wordBreak(String s, List wordDict) {
    if (s == null || s.length() == 0) {
        return false;
    }
    
    int len = s.length();
    boolean[] breakable = new boolean[len + 1];
    breakable[0] = true;
    
    for (int i = 1; i <= len; i++) {
        for (int j = 0; j < i; j++) {
            if (breakable[j] && wordDict.contains(s.substring(j, i))) {
                breakable[i] = true;
                break;
            }
        }
    }
    
    return breakable[len];
}

此题目中,Dictionary 为 list,直接使用了 List.contains 来判断substring是否为 Dictionary 中的一个word,时间复杂度为 O(dictionary.size())。这有些类似 list.get(index) 的时间复杂度。LinkedList vs ArrayList. 这种问题不需要太较真,直接使用,复杂度计算时提出来就可以了,因为这并不是面试的重点。

实际工作中,这种问题内部使用时,应该直接声明 ArrayList 或 HashSet。避免不必要的开销。如果是 api,那就需要数据预处理了。

这道题目可以使用 Trie 来预处理构建 dictionary,该方法特别适合两种情况:1. dictionary 非常大; 2. 该function调用次数非常多。

这里还涉及到 string splitdp 对应string方式 中 index 的使用方式。这是 string 中常遇到且通用的问题。这里使用的 dp 对应 string 的方式如下:

String value a b c d e
string index 0 1 2 3 4
dp index 0 1 2 3 4 5
string split 0 1 2 3 4 5

dp 对应 string 的方式同 string split 对应 string 的方式一致,对应 characters 中的间隙,而非 character 处。

  • dp[m]s.substring(0, m) 是否 breakable; s.substring(0, m) 值不包含character s.charAt(m);
  • 当计算dp[n] (n > m)时,对应为 s.substring(m, n) starting from index m

目前理解,这种对应方式逻辑上简易清晰,且不容易出错误。



140. Word Break ii

这道题目要求列出所有 breakable 的组合。DP 并不能像139中带来优化,因为 139 中DP只是判断 index 处是否 breakable,而140 需要列出 index 处 breakable 的所有组合。直接 BackTracking:

public List wordBreakII(String s, List wordDict) {
    List res = new LinkedList<>();
    
    // to pass timeout repeated test cases.139 word break.
    if (!isBreakable(s, wordDict)) {
        return res;
    }
    
    List path = new LinkedList<>();
    helper(s, wordDict, path, res);
    return res;
}

private void helper(String str, List dictionary, List path, List res) {
    if (str.equals("")) {
        res.add(String.join(" ", path));    // Java 8
        return;
    }
    
    for (int i = 0; i < str.length(); i++) {
        String sub = str.substring(0, i + 1);
        if (dictionary.contains(sub)) {
            path.add(sub);
            helper(str.substring(i + 1), dictionary, path, res);
            path.remove(path.size() - 1);
        }
    }
}

要在过程中记录下所有的合法结果,中间的操作会使得算法的复杂度不再是动态规划的两层循环,因为每次迭代中还需要不是constant的操作,最终复杂度会主要取决于结果的数量,而且还会占用大量的空间,因为不仅要保存最终结果,包括中间的合法结果也要一一保存,否则后面需要历史信息会取不到。

九章的Solution如下,思路很直观:

public class Solution {
    public ArrayList wordBreak(String s, Set dict) {
        // Note: The Solution object is instantiated only once and is reused by each test case.
        Map> memo = new HashMap>();
        return wordBreakHelper(s, dict, memo);
    }

    public ArrayList wordBreakHelper(String s,
                                             Set dict,
                                             Map> memo){
        if (memo.containsKey(s)) {
            return memo.get(s);
        }
        
        ArrayList results = new ArrayList();
        
        if (s.length() == 0) {
            return results;
        }
        
        if (dict.contains(s)) {
            results.add(s);
        }
        
        for (int len = 1; len < s.length(); ++len){
            String word = s.substring(0, len);
            if (!dict.contains(word)) {
                continue;
            }
            
            String suffix = s.substring(len);
            ArrayList segmentations = wordBreakHelper(suffix, dict, memo);
            
            for (String segmentation: segmentations){
                results.add(word + " " + segmentation);
            }
        }
        
        memo.put(s, results);
        return results;
    }
}

Trie implementation from programcreek(will implement my own version later on in Data Structures):

class TrieNode {
    TrieNode[] arr;
    boolean isEnd;

    public TrieNode() {
        this.arr = new TrieNode[26];
        this.isEnd = false;
    }
 
}
 
public class Trie {
    private TrieNode root;
 
    public Trie() {
        root = new TrieNode();
    }
 
    public void insert(String word) {
        TrieNode p = root;
        for(int i=0; i

你可能感兴趣的:(Sequence DP - 139. Word Break && 140. Word Break ii)