LeetCode 139, 140

690 阅读3分钟

LeetCode 139 Word Break

链接:leetcode.com/problems/wo…

方法1:dfs + memo

时间复杂度:O(n2),我不知道单从记忆化上怎么分析这个复杂度,但是应该跟DP的时间复杂度一样

空间复杂度:O(n)

想法:这个题我觉得最直观的解法就是DFS,就是说搜到一个下标的时候,看着整个wordDict里面有没有一个word,使得当前下标往后的几个元素能正好对上word,如果能对上,就先认为这里能匹配,然后继续往后搜。有两个优化:

  1. 因为是只要返回true or false,只要在dfs当中找到一种可以的做法就结束了,不需要再进行任何搜索,因此我们采用boolean型DFS实现提前结束。
  2. 使用记忆化数组,我这里使用一个数组,长度与原输入的String s长度一样。然后memo有三种可能状态:不知道(还没搜过这个地方)、不行、行。我用的是int型数组,memo[i]=1代表从i这个下标(包含i)一直到s的最后,可以用wordDict里面的词拼出来,=-1代表不行,=0代表我不知道,还没搜过。

代码:

class Solution {
    private int[] memo;
    
    public boolean wordBreak(String s, List<String> wordDict) {
        memo = new int[s.length()];
        Set<String> wordSet = new HashSet<>();
        for (String word : wordDict) {
            wordSet.add(word);
        }
        
        return dfs(s, 0, wordSet);
    }
    
    private boolean dfs(String s, int index, Set<String> wordSet) {
        int n = s.length();
        if (index == n) {
            return true;
        }
        
        if (memo[index] == -1) {
            return false;
        }
        if (memo[index] == 1) {
            return true;
        }
        
        for (String word : wordSet) {
            int len = word.length();
            if (index + len <= n && word.equals(s.substring(index, index + len))) {
                if (dfs(s, index + len, wordSet)) {
                    memo[index] = 1;
                    return true;
                }
            }
        }
        
        memo[index] = -1;
        return false;
    }
}

方法2:DP

时间复杂度:O(n2)

空间复杂度:O(n2)

想法:这个做法我觉得直接想不是很直观,但如果能想到上面一种做法的话,这个应该也能想到。我感觉DP和DFS+memo有时候就是写法的不一样,一个是按递推式来写,一个是写成递归,但是里面的想法是一样的。dp长为n+1,dp[i]代表的是s字符串的substring(0,i)能不能由wordDict实现wordBreak。递推式的想法是,假设说当dp[j]=true的时候,在它之前一定有某个i,使得dp[i]是true,然后s.substring(i,j)在字典里。对于j的所有i,但凡有一个i能达到这一点,dp[j]就应该是true。这时候我们会发现,假如说字典里出现了字符串的前j个元素代表的子字符串,原则上来说肯定是可以的,我们应当认为这种情况下dp[j]为true,因此对照上面的递推式,在此问题中,我们应该认为dp[0]=true。

代码:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> wordSet = new HashSet<>();
        for (String word : wordDict) {
            wordSet.add(word);
        }
        
        int n = s.length();
        boolean[] dp = new boolean[n + 1];
        dp[0] = true;
        
        for (int j = 1; j <= n; j++) {
            for (int i = 0; i < j; i++) {
                if (dp[i] && wordSet.contains(s.substring(i, j))) {
                    dp[j] = true;
                    break;
                }
            }
        }
        
        return dp[n];
    }
}

LeetCode 140 Word Break II

链接:leetcode.com/problems/wo…

方法:dfs + memo

时间复杂度:O(2n)

空间复杂度:O(2n)

想法:跟上一题不一样的地方在于求出所有解,那么首先我们应该知道递归的时候返回的解里面,从index往后的都有哪些解,这样在递归的上一层就可以往这些解里面往字符串的前面加单词。因此首先肯定不能采用boolean型的搜索,改为List型,不管怎么样都会做全部的搜索,不再搜到一个就结束全局。然后memo也得记录index->List。总的来说跟上一问的框架还是比较像,但不会再出现提前结束搜索,因此时间复杂度也涨到了2^n。

代码:

class Solution {
    private Map<Integer, List<String>> memo = new HashMap<>();
    
    public List<String> wordBreak(String s, List<String> wordDict) {
        Set<String> wordSet = new HashSet<>();
        for (String word : wordDict) {
            wordSet.add(word);
        }
        
        return dfs(s, 0, wordSet);
    }
    
    private List<String> dfs(String s, int index, Set<String> wordSet) {
        if (memo.containsKey(index)) {
            return memo.get(index);
        }
        
        List<String> res = new ArrayList<>();
        int n = s.length();
        
        String suffix = s.substring(index, n);
        if (wordSet.contains(suffix)) {
            res.add(suffix);
        }
        
        for (String word : wordSet) {
            int len = word.length();
            if (index + len <= n && word.equals(s.substring(index, index + len))) {
                List<String> next = dfs(s, index + len, wordSet);
                for (String ne : next) {
                    res.add(word + " " + ne);
                }
            }
        }
        
        memo.put(index, res);
        return res;
    }
}