139. Word Break

题目描述

给定一个字符串数组作为词典,再给定一个字符串。判断一下用词典中的词是不是可以组成这个字符串。
注意:词典中的词可以使用多次;词典中不存在重复的词
例如:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释:可以分解为leet code 两个词。

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释:可以分解为apple pen apple 三个词

输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false
解释:没有匹配的词组成s

分析

暴力搜索

暴力搜索。例如输入s = “leetcode”。
从第0位开始找,substring(0,3)组成一个词,那接着判断剩余的能不能用词典组成。
时间复杂度最快情况下是O( n n n^n nn),就是每次向前走一步都是一个单词,而且还需要遍历到最后。例如s=“aaaaab”,wordDict=[“a”,“aa”,“aaa”,“aaaa”,“aaaaa”]
空间复杂度O(n)

public boolean wordBreak(String s, List<String> wordDict) {
        return wordBreak(s,wordDict,0);
    }
    private boolean wordBreak(String s, List<String> wordDict,int start) {
        if(start >= s.length()){
            return true;
        }
        for(int i=start;i<=s.length();i++){
            if(wordDict.contains(s.substring(start,i))){
                if(wordBreak(s,wordDict,i)){
                    return true;
                }
            }
        }
        
        return false;
    }

记忆化回溯

加缓存改进暴力搜索。如果已经被解决了的子问题,把结果保存下来。

	private Boolean[] memory;
    public boolean wordBreak(String s, List<String> wordDict) {
        memory = new Boolean[s.length()+1];
        return wordBreak(s,wordDict,0);
    }
    private boolean wordBreak(String s, List<String> wordDict, int start) {
        if(start >= s.length()){
            return true;
        }
        if(memory[start]!=null){
            return memory[start];
        }
        boolean result = false;
        for(int i=start;i<=s.length();i++){
            if(wordDict.contains(s.substring(start,i))){
                if(wordBreak(s,wordDict,i)){
                    result =  true;
                    break;
                }
            }
        }
        memory[start] = result;
        return memory[start];
    }

动态规划

暴力搜索的逻辑是判断从0到i的子串可以被分解,继续判断从i+1到n的子串是不是能被分解。从整体来看。
例如"leetcode",wordBreak(0,8) 可以分解为wordBreak(0,4)+wordBreak(4,8),分解为2个子问题。
例如“catsanddog”,wordBreak(catsanddog) 可以分解为wordBreak(catsand)和wordBreak(dog)两个子问题,wordBreak(catsand) 又可以进一步分解为wordBreak(cat)和wordBreak(stand)两个子问题。
其实暴力搜索实现的就是这样的过程。
那和动态规划有什么不同呢?

我们假设 boolean[] dp = new boolean[n+1],dp[i]=true表示子串(0,i)能够被分解。基本条件是dp[0]=true,也就是说空字符串是可以被分解的。

动态转移方程:
d p [ i ] = t r u e , 当 且 仅 当 d p [ j ] = t r u e , 并 且 子 串 ( j , i ) 在 词 典 中 , 0 < = j < i dp[i]=true,当且仅当dp[j]=true,并且子串(j,i)在词典中,0<=j<i dp[i]=true,dp[j]=true(j,i),0<=j<i

之前在想动态转移方程的时候会考虑dp[i]与dp[i-1]是什么关系。例如 LeetCode 62. Unique Paths 动态方程是: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i−1][j]+dp[i][j−1] dp[i][j]=dp[i1][j]+dp[i][j1]

有时候也会找dp[i]和dp[i-1],dp[i-2]的关系,例如菲波那切数列类似的题目Leetcode 746. Min Cost Climbing Stairs动态方程是: d p [ i ] = m i n ( d p [ i − 1 ] + c o s t [ i − 1 ] , d p [ i − 2 ] c o s t [ i − 2 ] ] ) dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]cost[i-2]]) dp[i]=min(dp[i1]+cost[i1],dp[i2]cost[i2]])

而这里不确定具体是由哪个前面的状态转化过来的。具体是哪个子串关键是要符合两个条件,也有可能不能从任何一个前面的状态中转换过来。这应该是这个题目的难点,和不同之处。

在查找动态方程的时候,不要定式思维到dp[i-1],而是考虑从0到i-1,符合什么样的条件就可以转移到状态到i。有时候还要考虑是不是可以先获得dp[i+1],才能求得dp[i],例如查找最长的回文

	public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();
        boolean[] dp = new boolean[n+1];
        dp[0] = true;
        for(int i=1;i<=n;i++){
            for(int j = 0;j<i;j++){
                if(dp[j] && wordDict.contains(s.substring(j,i))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[n];
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值