这次分享一道关于动态规划的leetcode,单词拆分。
单词拆分
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以由 “leet” 和 “code” 拼接成。
class Solution {public boolean wordBreak(String s, List<String> wordDict) {boolean[] dp = new boolean[s.length() + 1];dp[0] = true;for(int i = 0;i <= s.length();i++){for(int j = 0;j <= i;j++){if(dp[j] && wordDict.contains(s.substring(j, i))){dp[i] = true;}}}return dp[s.length()];}
}
先看递推公式,举个例子,字符串为"appendapple",字符串列表为[“app”,“end”,“apple”],如果已经知道字符串append可以由字符串列表中单词构成,那么只要判断apple是否包含在字符串列表中即可,因此可以有递推公式,dp[j] && wordDict.contains(s.substring(j, i))
。解释一下,dp[j]表示下标0到下标j - 1这段字符串可以由字符串列表单词构成(可能有多个),剩下的只需要判断下标j到i的字符串是否出现在字符串列表中(下标j到i为一个字符串,即一个单词)。因此dp[i]的含义为以下标i - 1结尾的字符串可以(不可以)由给定的字符串列表中的单词构成,注意是i - 1,dp[i]为true,可以,dp[i]为false,则不可以。为什么是i - 1,这是由于substring方法截取字符串时区间是左闭右开的。最后是创建dp,长度为字符串长度加1,数组初始化,仅需将dp[0]初始化为true即可,为什么等会说明。
以上述示例说明代码的执行流程,首先初始化dp[0] = true,接着进入for循环,当i = 0,j = 0时,substring(0, 0),为空。当i = 1,j = 0时,substring(0, 1),为l,不再字符串列表中,j++,substring(1, 1)为空。当i = 2,j = 0,substring(0, 2)为le,j++,substring(1, 2)为e,j++,substring(2,2)为空,均不在字符串列表中。当i = 3,j等于0,substring(0, 3)为lee,j++,substring(1, 3)为ee,substring(2,3)为e,substring(3,3)为空,均不在字符串列表中。当i = 4,j等于0,substring(0, 4)为leet,出现在字符串列表中,wordDict.contains(s.substring(j, i)结果为true,dp[j]即为dp[0],这时就涉及到dp[0]的初始化,如果初始化为false,导致dp[4]无法初始化,因此dp[0]初始化为true,因此dp[4] = true,可以判断了leet出现在字符串列表中,虽然是dp[4],但是仅判断了下标0到3,并没有包括下标4。j++,substring(1, 4)为eet,substring(2, 4)为et,substring(3, 4)为t,substring(4,4)为空,均不在字符串列表中。接着当i = 5,截取的字符串为leetc,eetc,etc,tc,c和空。接着当i = 6是,截取的字符串为leetco,eetco,etco,tco,co,o和空,接着i = 7,截取的字符串为leetcod,eetcod,etcod,tcod,cod,od,d和空,均不存在于字符串列表中。当i = 8,j = 0,截取字符串为leetcode,eetcode,etcode,tcode,,均不存在于字符串列表中。当i = 8,j = 4,substring(4, 8)截取的字符串为code,出现在字符串列表中,并且dp[4]为true,因此dp[8]为true,之后截取的字符串为ode,de,e和空,,均不存在于字符串列表中。内外层循环遍历结束,返回dp[字符串长度 + 1],leetcode长度为7,返回dp[8]为true,表示的含义是下标0-7的字符串可以由字符串列表中的单词构成。