diff --git a/Leecode/src/main/java/com/markilue/leecode/dynamic/T17_WordBreak.java b/Leecode/src/main/java/com/markilue/leecode/dynamic/T17_WordBreak.java new file mode 100644 index 0000000..06fe8a9 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/dynamic/T17_WordBreak.java @@ -0,0 +1,153 @@ +package com.markilue.leecode.dynamic; + +import org.junit.Test; + +import java.lang.reflect.Array; +import java.util.*; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.dynamic + * @Author: markilue + * @CreateTime: 2022-12-08 10:38 + * @Description: + * TODO 力扣139题 单词拆分: + * 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 + * 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 + * @Version: 1.0 + */ +public class T17_WordBreak { + + @Test + public void test(){ + String s = "leetcode"; + + List wordDict = new ArrayList<>(Arrays.asList("leet", "code")); + System.out.println(wordBreak(s,wordDict)); + } + + @Test + public void test1(){ + String s = "applepenapple"; + + List wordDict = new ArrayList<>(Arrays.asList("apple", "pen")); + System.out.println(wordBreak(s,wordDict)); + } + + /** + * 思路:实际上就是判断能重复用的wordDict能否构造出s + * TODO 动态规划五部曲: + * (1)dp定义:dp[i][j]使用[0-i]的wordDict能否构造出s[0-j] + * (2)dp状态转移方程:dp[j]=dp[j]||(dp[j-wordDict[i].length]&&wordDict[i]==s.subString(j-wordDict[i].length)) + * (3)dp初始化:dp[0]=true + * (4)dp遍历顺序:跟两个for的顺序有关系,因为word可以反复利用,只需要判断最后的word + * (5)dp举例推导: + * 速度击败71.72%,内存击败60.22% + * @param s + * @param wordDict + * @return + */ + public boolean wordBreak(String s, List wordDict) { + boolean[] dp = new boolean[s.length()+1]; + dp[0]=true; + + + for (int j = 0; j < dp.length; j++) { + for (int i = 0; i < wordDict.size(); i++) { + String word = wordDict.get(i); + if(j>=word.length()){ + String b = s.substring(j-word.length(),j); + dp[j]=dp[j]||(dp[j-word.length()]&&word.equals(b)); + } + + } + } + + return dp[s.length()]; + } + + + /** + * 官方另一种背包,找到之后直接break是精髓,但是就看不出dp的状态转移方程了 + * 速度击败92.41%,内存击败87.5% + * @param s + * @param wordDict + * @return + */ + public boolean wordBreak2(String s, List wordDict) { + boolean[] dp = new boolean[s.length() + 1]; + dp[0] = true; + + for (int i = 1; i <= s.length(); i++) { + for (String word : wordDict) { + int len = word.length(); + if (i >= len && dp[i - len] && word.equals(s.substring(i - len, i))) { + dp[i] = true; + break; + } + } + } + + return dp[s.length()]; + } + + + /** + * 官方及代码随想录的的动态规划解法,利用hashset加快速度 + * 速度击败33.98%,内存击败7.9% + * @param s + * @param wordDict + * @return + */ + public boolean wordBreak1(String s, List wordDict) { + HashSet set = new HashSet<>(wordDict); + boolean[] valid = new boolean[s.length() + 1]; + valid[0] = true; + + for (int i = 1; i <= s.length(); i++) { + for (int j = 0; j < i && !valid[i]; j++) { + if (set.contains(s.substring(j, i)) && valid[j]) { + valid[i] = true; + } + } + } + + return valid[s.length()]; + } + + + /** + * 回溯+记忆化搜索:本质上就是把已经用过的东西存起来,便于剪枝 + * 速度击败79.1%,内存击败24.38% + */ + private Set set; + private int[] memo; + public boolean wordBreak3(String s, List wordDict) { + memo = new int[s.length()]; + set = new HashSet<>(wordDict); + return backtracking(s, 0); + } + + public boolean backtracking(String s, int startIndex) { + // System.out.println(startIndex); + if (startIndex == s.length()) { + return true; + } + if (memo[startIndex] == -1) { + return false; + } + + for (int i = startIndex; i < s.length(); i++) { + String sub = s.substring(startIndex, i + 1); + // 拆分出来的单词无法匹配 + if (!set.contains(sub)) { + continue; + } + boolean res = backtracking(s, i + 1); + if (res) return true; + } + // 这里是关键,找遍了startIndex~s.length()也没能完全匹配,标记从startIndex开始不能找到 + memo[startIndex] = -1;//关键在于这里,添加了记忆,即从startIndex开始是不能找到,以后到这就不用继续往下回溯了 + return false; + } +}