leetcode_139. 单词拆分(dp + dfs + 记忆化 dfs )

题目描述

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:

输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。

分析:第一感觉是用回溯法做,从字符串 s 开头开始,判断字典中的单词是否匹配 s ,用回溯法做,最终只要有一个能完全匹配,即可返回 True
但是这个处理过程, 有点难以下手,参考别人的做法后,用 dp 来做,不看别人思路一脸懵逼,看了恍然大悟,还是要多练多总结 dp 套路呀!
这个题属于 dp 中的字符串问题,

  • 一般是 以字符串 第 i 个结尾的字符串的最优策略,比如本题可延伸为字符串相对于字典的最大匹配长度,
  • 前 i 个字符串的最优策略,最后一个字符串可以用上,可以不用。
    本题属于第一种情况:
  • dp[i]: s[0,1,i-1] 能否拆分,能为 true, 不能为 false. 通常这种情况,我们将 dp 数组长度设置为 s.lengh + 1 ,dp[0] = true. dp[i] 的状态取决于dp[0,j,i-1],如果有一个 dp[j] + word = s[:i], word 属于 wordDict,那么 dp[i] = true; dp[0] = true,可理解为不需要转换,就可拆分匹配。

解法二 : 回溯法
过程: 如 s = “leetcode” ,wordDict = [“leet”,“code”]

  • “leetcode” 能否 break ,分解 为 “l" 是否在单词表中,和剩余的子串能否 break
  • 回溯法考虑所有的可能情况,用指针 start 的位置代表各个位置的状态
  • 如果指针的左侧的子串,是在单词表中,则只需要对以指针为开头的剩余子串进行递归掉用。
public class lc_139_wordBreak {
    //方法一 :dp
    public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        if(n == 0 ) return true;
        boolean [] dp = new boolean[n + 1];
        HashSet<String> set = new HashSet<>(wordDict);
        dp[0] = true;
        for(int i = 1 ; i <= n ; i ++){
            for(int j = 0 ; j < i ; j ++){
                String tmp = s.substring(j,i);
                if(dp[j] && set.contains(tmp)){
                  dp[i] = true;
                  break;
                }
            }
        }
        return dp[n];
    }
 }

解法二 : 未剪枝的回溯法,回溯法,未剪枝前,超时,比如案例 s = “aaaaaaaaaaaaaa…aab”,wordDict = [“a”,“ab”]; 每一个都会进入分支。

public class lc_139_wordBreak {
    public boolean wordBreak1(String s, List<String> wordDict) {
        int n = s.length();
        if (n == 0) return true;
        HashSet<String> set = new HashSet<>(wordDict);
        int start = 0 ;
        boolean res = dfs(s,set,start);
        return res;
    }

    private boolean dfs(String s, HashSet<String> set, int start) {
        //终止条件
        if(start > s.length()-1){
            return true;
        }
        for(int end = start + 1; end <= s.length(); end ++){
            String word = s.substring(start,end);
            if(set.contains(word) && dfs(s,set,end)){
                return true;
            }
        }
        return false;
    }
}

增强版:记忆化回溯法,增加一个字典来记录已经得到的子树结果,遇到重复子树直接返回。

public class lc_139_wordBreak {
Map<String,Boolean> memo = new HashMap<>();
    public boolean wordBreak2(String s, List<String> wordDict) {
        int n = s.length();
        if (n == 0) return true;
        HashSet<String> set = new HashSet<>(wordDict);
        return dfs1(set,s);

    }

    private boolean dfs1(HashSet<String> set, String s) {
        if(s.equals("")) return  true;

        if(memo.containsKey(s)) return memo.get(s);

        //遍历所有的分割位置
        for(int i = 1; i < s.length() + 1; i++){
            //获取一段字符串
            String cur = s.substring(0,i);

            //如果当前这个集合中没有这个单词,continue
            if(!set.contains(cur)) continue;

            //如果有,dfs
            if(dfs1(set,s.substring(i))) {
                memo.put(cur, true);
                return true;
            }
        }
        //如果此时以 s 开头不能分裂
        memo.put(s,false);
        return false;
    }

}

总结:本题可作为回溯法 + 记忆化 一个很好的练习。
当然能想起来 dp ,首选 dp.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值