leetcode - [139]单词拆分|刷题打卡

208 阅读3分钟
我是通过idea的leetcode插件获取题目做题的,所以题目描述基本都会有注释符号哈

​ 这套题解题的时候参照LeetCode的其中一个方案,链接如下

单词划分 (官方解法,对应method2)

Java 动态规划优化:最长最短单词 (这个很dio,建议看, 对应 method3)

1. 题目描述

 * [139] 单词拆分
 * 给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
 *
 * 说明:
 *
 * 拆分时可以重复使用字典中的单词。
 * 你可以假设字典中没有重复的单词。
 * 示例 1:
 *
 * 输入: s = "leetcode", wordDict = ["leet", "code"]
 * 输出: true
 * 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
 * 示例 2:
 *
 * 输入: s = "applepenapple", wordDict = ["apple", "pen"]
 * 输出: true
 * 解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
 *      注意你可以重复使用字典中的单词。
 * 示例 3:
 *
 * 输入: s = "cats sand og", wordDict = ["cats", "dog", "sand", "and", "cat"]
 * 输出: false
 *
 * Related Topics 动态规划

2. 思路分析

​ 这个题目有点类似之前的分割回文数

​ 我们的思路也是考虑从分割字符串,分为左串和右串入手,左串成功了,在判断右串是否也满足

​ 以及引入一个到具体字符串的某一位是否满足拆分的结果集,最终我们可以通过结果集来返回我们想要的结果。

​ 因此,我们的工作内容就是填充结果集,判断在某一个点切分开的字符串,是否是满足单词拆分的要求。引用一段method3方法的描述。

一般动态规划:

如果[0,k]可以被分词字典里的单词,检查[k + 1, i]也是字典里的单词,那么[0,i]也可以被分成字典里的单词 定义dp[i],dp[i]的含义是,前i个字符组成的子串能否被分割,令dp[0] = true 得出dp[i] = dp[k] && check(k + 1, i),check函数是判断该子串是否在字典内

​ 需要注意,method1的 boolean数组记录的是 下标开始到字符串结束的结果是否符合要求,method2 和 method3 的数组记录的是 字符串的第几位到起点的结果。 method2和method3的数组会比字符串的长度大1

3. AC代码

public class No139 {

    public boolean wordBreak(String s, List<String> wordDict) {
        return method1(s, new HashSet<String>(wordDict), 0, new Boolean[s.length()]);
//        return method2(s,wordDict);
//        return method3(s,wordDict);
    }

    /**
     *
     *
     * 执行耗时:6 ms,击败了77.33% 的Java用户
     * 内存消耗:38.7 MB,击败了58.73% 的Java用户
     *
     * @param s 判断的字符串
     * @param dirt 字典
     * @param start 字符串的偏移量
     * @param memo 用来记录字符串的每个偏移量往后能否被拆分的数组
     * @return
     */
    private boolean method1(String s, Set<String> dirt, int start, Boolean[] memo) {
        // 到了最后一位
        if(start == s.length()) {
            return true;
        }

        // 如果数据字典对于该偏移量的结果已经有了,直接返回
        // 这个结果数组会在处理过程中慢慢完善
        if(memo[start] != null) {
            return memo[start];
        }
        //end <= 因为subtring的末尾是不算的,要不然会少一位
        for (int end= start+1;end<=s.length();end++) {
            // 分为左串 和 右串,左串是符合要求的,再来判断右串是否符合要求
            if(dirt.contains(s.substring(start,end)) && method1(s, dirt, end, memo)) {
                return memo[start] = true;
            }
        }
        return memo[start] = false;
    }

    /**
     * 来自 leetCode 解法 [单词划分]:
     * https://leetcode-cn.com/problems/word-break/solution/dan-ci-chai-fen-by-leetcode-solution/
     *
     * 执行耗时:9 ms,击败了58.49% 的Java用户
     * 内存消耗:38.7 MB,击败了58.73% 的Java用户
     *
     * @param s
     * @param wordDict
     * @return
     */
    private boolean method2(String s, List<String> wordDict) {
        Set<String> wordDictSet = new HashSet(wordDict);
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;

        // 嵌套循环,构造结果集
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

    /**
     *
     * 来自 leetCode 解法: Java 动态规划优化:最长最短单词
     * https://leetcode-cn.com/problems/word-break/solution/java-dong-tai-gui-hua-you-hua-zui-chang-9fou3/
     *
     * 执行耗时:1 ms,击败了99.74% 的Java用户
     * 内存消耗:36.8 MB,击败了93.27% 的Java用户
     *
     * @param s
     * @param wordDict
     * @return
     */
    private boolean method3(String s, List<String> wordDict) {
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        int maxLen = Integer.MIN_VALUE, minLen = Integer.MAX_VALUE;
        Set<String> set = new HashSet<>();
        for (String word : wordDict) {
            set.add(word);
            maxLen = Math.max(maxLen, word.length());
            minLen = Math.min(minLen, word.length());
        }

        for (int i = 1; i < dp.length; i++) {
            int near = i - minLen, far = i - maxLen;
            // 节省了循环的次数,也节约了 dp数组的空间
            for (int start = near; start >= 0 && start >= far; start--) {
                if (dp[start] && set.contains(s.substring(start, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}

4. 总结

​ 在完成题目的同时,可以考虑是否还能更上一层楼,突破自己,仔细挖掘一下题目的内容(折射现实就是需求的内容以及实现的代码),是否可以更加快速的实现功能。

​ 也就是京东的slogan ---- 多快好省 【手动狗头】


​ 本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情