LeetCode - 139. Word Break & Word BreakII (dp)

发布于 2024-09-08 11:00:29 字数 5987 浏览 12 评论 0

题目

解析

记忆化的思路:

  • 字符串在每个位置从左到右进行划分成左边和右边部分;
  • 左边部分递归的去求看是否满足,右边看 wordDict 中是否有这个单词;
  • 因为递归的时候有重复的子问题,所以使用 map 进行记忆化;
  • 这里是从顶(例如 "leetcode" 从最长求到最短)到下,而 dp 是从短推到长;
  • 这里每个位置只需要某个划分满足即可,所以下面递归函数中如果有一个划分满足条件,立即就 return 了;

class Solution {

    private HashMap<String, Boolean> dp;

    public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null)
            return true;
        if (wordDict == null)
            return false;
        // memorize
        dp = new HashMap<>();
        return rec(s, wordDict);
    }

    private boolean rec(String s, List<String> dict) {
        if (dict.contains(s)) //这句话不能省略 因为下面只是判断到 < sb.length,
            return true;
        if (dp.containsKey(s))
            return dp.get(s);
        for (int i = 0; i < s.length(); i++) { // for(int i = 1; i < s.length(); i++){ 也可以写成从 1 开始 因为 L == ""可以不用划分
            String L = s.substring(0, i); // [0,i)
            String R = s.substring(i);   //  [i,sb.length)
            if (dict.contains(R) && rec(L, dict)) {//先判断右半部分
                dp.put(s, true);
                return true;
            }
        }
        dp.put(s, false);
        return false;
    }
}

注意到,也可以反过来改成 L 去判断在不在 wordDic t 中,而右边部分 R 去递归(这样代码简介很多,而且速度也更快)

class Solution {
    
    private HashMap<String, Boolean> dp;
    
    public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null)
            return true;
        if (wordDict == null)
            return false;
        dp = new HashMap<>(); 
        return rec(s, wordDict); 
    }

    //相当于 s 的左边 L 去判断在不在 wordDict 中,而右边去递归
    private boolean rec(String s, List<String> dict) {
        if (s.isEmpty())
            return true;  //""返回 true
        if (dp.containsKey(s))
            return dp.get(s);
        for (String word : dict) {
            if (s.startsWith(word)) {//  左边 L 在 wordDict 中有包含
                if (rec(s.substring(word.length()), dict)) {
                    dp.put(s, true);
                    return true;
                }
            }
        }
        dp.put(s, false);
        return false;
    }
}

递推 dp, 也就是从下到上的求解:

  • 注意,一开始 dp.put("",true) ,表示的是相当于 "" 是返回 true 的,这个是必须的。因为某个划分 L"" ,而 RwordDict 中。
  • 比如划分到 "leetcode""leet" 的时候,当 leet 被划分成 """leet" 左边就是 "" ,右边是 "leet" (在 wordDict 中),所以满足;
class Solution {

    public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null)
            return true;
        if (wordDict == null)
            return false;
        HashMap<String, Boolean> dp = new HashMap<>();
        dp.put("", true);// must
        for (int i = 1; i <= s.length(); i++) {// 从 1 开始就可以 因为 dp.put("",true)
            String sI = s.substring(0, i);
            for (int j = 0; j < i; j++) {
                String L = sI.substring(0, j);
                String R = sI.substring(j);
                if (dp.get(L) != null && dp.get(L) && wordDict.contains(R)) {
                    dp.put(sI.toString(), true);
                    break;
                }
            }
        }
        return dp.get(s) == null ? false : dp.get(s);
    }
}

稍微优化的思路:

  • 上面的 HashMap 中的 true 其实是表示的 dp 的值,因为 keyString ,所以不好用数组表示。
  • 但是其实我们可以将左边部分的字符串映射为只需要以某个位置结尾的字符串就可以了。
  • 也就是说 L 部分求解,只需要记录那个字符串的结束位置即可,也就是可以只用一个 boolean 数组求解即可。
class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null)
            return true;
        if (wordDict == null)
            return false;
        StringBuilder sb = new StringBuilder(s);
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true; // 类似 dp.put("",true);
        for (int i = 1; i <= sb.length(); i++) {//从 1 开始就可以
            String sbI = sb.substring(0, i);
            for (int j = 0; j < i; j++) {
                if (dp[j] && wordDict.contains(sbI.substring(j))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

再次优化,连 sbI 也可以省略,因为可以直接取 [j,i) 之间的字符作为 sbI 即可。

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null)
            return true;
        if (wordDict == null)
            return false;
        StringBuilder sb = new StringBuilder(s);
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true; //  类似 dp.put("",true);
        for (int i = 0; i <= sb.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && wordDict.contains(sb.substring(j, i))) {//这里简单的优化
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

零度°

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

我们的影子

文章 0 评论 0

素年丶

文章 0 评论 0

南笙

文章 0 评论 0

18215568913

文章 0 评论 0

qq_xk7Ean

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文