方法1:递归

很明显,这是个递归的问题。我们应该能比较轻松的写出下面的代码:

import java.util.*;
public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {
        if (s.length()==0)
            return true;
        for (int i=1;i<=s.length();++i){
            String sub=s.substring(0,i);
            if (dict.contains(sub)&&wordBreak(s.substring(i),dict))
                return true;
        }
        return false;
    }
}

但是这个代码在执行下面的测试样例(这个样例是在LeetCode上拿到的)时,会超时。
图片说明
为什么会这样呢?想一个简单一点的,s的值为aaaaab,dict的值为"a", "aa", "aaa", "aaaa", "aaaaa",那递归下去,第一次递归结束,是a不匹配b。然后再去循环判断ab中的每个子串是否在dict中,然后再去判断aab中每个子串是否在dict中。有没有发现什么,aab中,ab子串已经判断过了,递归的过程中又进行了一次重复判断。这样,这上面的方法的时间复杂度就是O(nn),时间复杂度超级高了。那既然有很多重复判断,我们何不用一个容器将以前判断的结果记录下来,后面判断的时候直接用呢。所以有了下面优化的版本:

import java.util.*;
public class Solution {
    HashMap<String,Boolean> flag=new HashMap<>();
    public boolean wordBreak(String s, Set<String> dict) {
        if (s.length()==0)
            return true;
        if (flag.containsKey(s))
            return flag.get(s);
        for (int i=1;i<=s.length();++i){
            String sub=s.substring(0,i);
            boolean b=dict.contains(sub);
            flag.put(sub,b);
            if (b&&wordBreak(s.substring(i),dict))
                return true;
        }
        return false;
    }
}

但是这种太占内存了,当字符串长度很长的时候,其子串会非常多,导致HashMap占用内存非常大。搬运LeetCode里面的解法,代码如下:

public boolean wordBreak(String s, Set<String> wordDict) {
        return word_Break(s, wordDict, 0, new Boolean[s.length()]);
    }
    public boolean word_Break(String s, Set<String> wordDict, int start, Boolean[] memo) {
        if (start == s.length()) {
            return true;
        }
        if (memo[start] != null) {
            return memo[start];
        }
        for (int end = start + 1; end <= s.length(); end++) {
            if (wordDict.contains(s.substring(start, end)) && word_Break(s, wordDict, end, memo)) {
                return memo[start] = true;
            }
        }
        return memo[start] = false;
    }

方法2:动态规划

讨论里面很多都是用动态规划的方法做的,其基本原理是将一个字符串分成两个部分,如果两个部分都满足要求,则总的字符串是满足要求的,这两个部分又可以继续去判断。具体代码如下:

    public boolean wordBreak(String s, Set<String> wordDict) {
        int len = s.length();

        boolean[] flags = new boolean[len+1];
        flags[0] = true;

        for (int i = 1; i <= len; i++) {
            for (int j = 0; j < i; j++) {
                if (flags[j] == true && wordDict.contains(s.substring(j,i))) {
                    flags[i] = true;
                    break;
                }
            }
        }

        return flags[len];
    }

方法3:BFS

搬运自LeetCode官方题解,自己跑一遍应该能看懂。

    public boolean wordBreak(String s, Set<String> dict) {
        Queue<Integer> queue = new LinkedList<>();
        int[] visited = new int[s.length()];
        queue.add(0);
        while (!queue.isEmpty()) {
            int start = queue.remove();
            if (visited[start] == 0) {
                for (int end = start + 1; end <= s.length(); end++) {
                    if (dict.contains(s.substring(start, end))) {
                        queue.add(end);
                        if (end == s.length()) {
                            return true;
                        }
                    }
                }
                visited[start] = 1;
            }
        }
        return false;
    }

但上面的代码还有优化的空间。我们看到,红框部分,是从end到s.length遍历的,对应的子串长度为end-start。这就导致,有一些不可能出现的子串被判断了。假设dict中,最长的单词为3个字母,那当(end-start)>3时,就没必要再去判断了。
图片说明
所以,我们再做一下优化,优化代码如下:

    public boolean wordBreak(String s, Set<String> dict) {
        Queue<Integer> queue = new LinkedList<>();
        int[] visited = new int[s.length()];
        queue.add(0);
        //扫描一遍dict,记录最长的单词长度
        int maxLen=-1;
        for (String item:dict){
            if (item.length()>maxLen)
                maxLen=item.length();
        }
        while (!queue.isEmpty()) {
            int start = queue.remove();
            if (visited[start] == 0) {
                for (int end = start + 1; end <= s.length(); end++) {
                    if ((end-start)<=maxLen&&dict.contains(s.substring(start, end))) {
                        queue.add(end);
                        if (end == s.length()) {
                            return true;
                        }
                    }
                }
                visited[start] = 1;
            }
        }
        return false;
    }

经过测试,使用BFS的优化过的效率最高。