动态规划的思想来求解字符串分割问题

LeetCode WordBreak原题

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given
s = "leetcode",
dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".

 

0,问题介绍

给定一个包含了若干单词的字典以及一个字符串,将该字符串分割,分割后的得到的单词必须由字典中的单词组成。

 

1,动态规划思想的介绍---参照《算法导论》中关于动态规划的分析来分析此问题

①最优子结构

要判断整个字符串 s[0..length] 能否被分割,可先判断 s[0...length -1] 能否被分割;而判断 s[0...length -1] 能否被分割,可先判 s[0...length-2] 能否被分割,……直至判断 s[0] 能否被分割,而 s[0] 能否被分割是显而易见的---(查找 s[0] 在不在字典中即可--dict.contains(s[0])???)

递归表达式如下:

 

②子问题的总个数是多少?每个子问题可以有多少种选择?算法的时间复杂度是多少?

子问题的个数一个有 n 个,n为字符串的长度。每个子问题有 2 种选择,即要么可以分割,要么不可以分割。从这个角度来看,算法的时间复杂度为 O(n)。但是,这里没有考虑每个子问题做选择时,需要执行多少步骤,代价是多大?从下面代码的第 18 行 for 循环中可以看出,第 i 个子问题 需要循环 i 次,那么时间复杂度为 1+2+3+……+n = O(n^2)。

③用到了 动态规划中的重叠子问题

动态规划中的重叠子问题是指,在将原问题分解的过程中,得到了若干子问题,再将这些子问题分解时,又得到若干更小子问题……,这些子问题中,有很多是重复的。这个特点与分治算法分解问题时不同,分治算法分解得到的子问题一般是独立的,各个子问题之间没有太多联系。基于动态规划子问题的重复性,因此,在求解出某个子问题之后,将它的结果记录下来,当下一次再碰到此问题时,直接查找它的结果,而不是再一次计算该子问题。

在 wordBreak 问题中的第2点动态规划求解思路分析(下面 第2点)中,求解 match[i] 的值可能需要用到 match[i-1]、match[i-2]、……match[0]的值;

求解match[i-1]的值 可能需要用到 match[i-2]、match[i-3]、match[0]。match[i] 即对应 s[0..i-1]能否分割这个问题,再结合动态规划自底向上求解问题的性质,把"底层"问题的求解结果记录下来,在求解到“上层”问题时,查找“底层”问题的结果,这种方式可以提高算法的运行效率。这也是《算法导论》中求LCS问题中提到的“查表”。具体的代码体现如下:求mathc[i]的值时,需要查找match[j]的值是多少。

for (int i = 1; i < length + 1; i++) {
                for (int j = 0; j < i; j++) {
                    if (match[j] && wordDict.contains(s.substring(j, i))) {
                        match[i] = true;

 

2,用动态规划求解的思路

①match[s.length]  用来表示字符串 s[0...length-1]  每部分能否分割。初始时,match[0]=true; match[i] 表示 s[0...i-1] 这段字符能否分割。

match[s.length] 则表示整个字符串(s[0...length-1])能否分割。

②若match[i]=true,表示 s[0...i-1]能够分割,则需要满足以下条件中的任一 一个:

a)match[0]==true && s[0...i-1] 在字典中;b)match[1] == true && s[1...i-1] 在字典中;c)match[2] == true && s[2...i-1]在字典中;.....

d)match[i-1]==true && s[i-1] 在字典中。 s[i...j]在字典中表示:字符串s中由下标为 i 到 j 的子字符串是字典中的某个单词。

具体分析:设 s = "aaaaa",dict = ["aaaa","aaa"]

由于 "a" 不在dict中,故match[1] = false; "aa" 不在dict中 且 (match[1]=false && "a" 不在dict中),故match[2]=false,对于 match[3]的值,

先判断 "aaa" 是否在dict中,由于 "aaa"在dict 中,故 match[3] = true,对于match[4],由于"aaaa"在dict中故 match[4] = true,

对于 match[5],先判断 "aaaaa",由于它不属于dict ;再继续判断,(match[1] = true?) and (s[1...5] exist in dict?),虽然,s[1...5]="aaaa" exist in dict 为true,但是 match[1] = false,故此时还不能判断match[5];

"aaaaa" 先分成 "a" ,再分成 "aaaa" 是否可以?
由于 "a" 不在 dict中 因为,match[1] == false
尽管 "aaaa" 在 dict 中,但还是故不能这样分,因为 "a" 不在 dict 中
故match[2] 赋值为 false

 

再继续判断,(match[2] = true?) and (s[2...5] exist in dict?)....

"aaaaa" 先分成 "aa",再分成 "aaa",是否可以?
由于 "aa" 不在 dict 中,因为判断match[2] == false.
尽管 "aaa" 在dict 中,但还是不能这样分
故match[3]  赋值为 false

直至判断到

match[4]==true? and s[4...4] == "a" exist in dict?? 此时,尽管match[4] == true 但是 s[4]== "a" 为 false,故match[5]= false.

即对于 设 s = "aaaaa",dict = ["aaaa","aaa"],程序将返回false.

 

3,完整代码如下:

 1     import java.util.HashSet;
 2     import java.util.Set;
 3 
 4     public class Solution {
 5 
 6         public boolean wordBreak(String s, Set<String> wordDict) {
 7             // 参数校验
 8             if (s == null || s.length() < 1 || wordDict == null || wordDict.size() < 1) {
 9                 return false;
10             }
11 
12             // 标记是否匹配,match[i]表示 str[0, i-1]可以分割
13             int length = s.length();
14             boolean[] match = new boolean[length + 1];
15             match[0] = true;
16 
17             for (int i = 1; i < length + 1; i++) {
18                 for (int j = 0; j < i; j++) {
19                     if (match[j] && wordDict.contains(s.substring(j, i))) {
20                         // s(0,n) = s(0,i) + s(i,j) + s(j,n)
21                         match[i] = true;
22                         break;
23                     }
24                 }
25             }
26             return match[length];
27         }
28         public static void main(String[] args) {
29             Solution s = new Solution();
30             Set<String> set = new HashSet<String>();
31             set.add("aaaa");
32             set.add("aaa");
33             boolean result = s.wordBreak("aaaaa", set);
34             System.out.println(result);
35         }
36         
37     }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值