[LeetCode] 139. Word Break

原题链接:https://leetcode.com/problems/word-break/

1. 题目介绍

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:
The same word in the dictionary may be reused multiple times in the segmentation.
You may assume the dictionary does not contain duplicate words.

给出一个非空的字符串 s ,以及一个非空的、包含很多字符串的字典 wordDict 。字符串 s 被拆分成多个子串,有很多种拆分方法。有没有一种拆分方法可以保证每个子串都在字符串字典 wordDict 中出现过?如果有,返回true,否则返回false

拆分后的子串可以重复使用 wordDict 中的字符串(比如Example2),并且保证字符串字典中没有重复的字符串。

Example 1:

Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true 
because "leetcode" can be segmented as "leet code".

Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true 
because "applepenapple" can be segmented as "apple pen apple".
Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false

2. 解题思路-动态规划

注:我的思路参考了 https://www.cnblogs.com/grandyang/p/4257740.html 的讲解。

这个题也是练习动态规划的好题目。我们需要构造dp数组、找出状态转移方程。

我们使用[ a , b )来描述一个子串,意思是从下标为 a 的字符开始,到下标为 b-1 的字符为止的字符串。比如 LeetCode 中的子串 L 就是[0 , 1 ),Le就是[ 0,2 ) ,LeetCode就是[0,8)。

一个字符串有很多种拆分的方式,比如 LeetCode 可以:
拆分为2段: L+eetCode ;
拆分为3段:例如 L + ee + Code ;
拆分为4段:例如 L + eet+ C +ode ;
·
·
·
拆分为8段:例如 L + e + e + t + C + o + d + e

而无论拆分为多少段,我们都可以看作是不断拆分成2段的结果。
想要拆分成3段,那就先要把LeetCode一分为二,然后再把其中某一段拆成2段。
LeetCode = Lee + tCode = L + ee + tCode;

想要拆分成4段,那就要把LeetCode先拆分成2段,然后把其中一段再拆分成2段,最后再挑出1段来一分为二。
LeetCode = Lee + tCode = L + ee + tCode = L + ee + tCo + de

于是,这就给了我们解题思路,只要将字符串不停地拆分为2段,分开的2段是否都在字典里。如果不在字典里,那么就继续分。

对于长度为 i 的字符串[ 0 , i ),需要一个 j 来把字符串分为 [ 0 , j ) 和 [ j , i )两段。当 [ 0 , j ) 和 [ j , i ) 都在字典里时,字符串[ 0 , i )就是满足条件的。

i 可以从 0 递增到 length, 如果随着 i 的递增,前面判断过的字符串[ 0 , i ),就是后面需要用到的[ 0 , j ) 。所以可以使用一个一维数组dp[ s.lenght() + 1]存储,dp[ i ]表示范围[ 0, i )内的字符串满足条件。最后dp[ s.lenght() ]的值就可以表示整个字符串[ 0 , s.lenght() )是否满足条件了。
状态转移方程:

dp[i] = (dp[j] == true) && ( 子串[j , i)在字典里 )
1 <= i <= s.length()
1 <= j < i

实现代码

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        int length = s.length();
        
        boolean dp[] = new boolean[length + 1];
        //dp[]数组中,false代表不知道或不满足条件,true代表满足
        
		for(int i = 1 ; i<=length ; i++ ) {
			//判断子串[0,i) 如果不经过拆分,是否出现在字典中
			if( wordDict.contains(s.substring(0 , i)) == true ) {
				dp[i] = true;
				continue;
			}
			
			//判断子串[0,i) 如果拆分为[0,j)和[j,i),两部分是否都出现在字典中
			for(int j = 1 ; j < i ; j++) {
				if( dp[j] == true && wordDict.contains(s.substring(j , i)) == true ){
					dp[i] = true;
					break;
				}
			}
		}
		
		return dp[length] ;
    }
}

最后再顺便提一下,这道题中,字典是以List的形式给出的,不是数组的形式,这个非常好。如果真的遇到以数组形式给出的题目,我们要做的第一件事就是把字典中的所有单词都存入HashSet集合中。不然我们每查一次单词,都要遍历一遍数组,多麻烦。把所有单词都存入集合中,就可以有常数时间级的查找速度。

3. 参考资料

https://www.cnblogs.com/grandyang/p/4257740.html

你可能感兴趣的:(LeetCode,动态规划,LeetCode,动态规划)