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


    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     }
  • 相关阅读:
    POJ-1182 食物链
    hdu 1879 继续畅通工程
    HDU 2604 Queuing
    hdu 1232 畅通工程
    POJ-1611 The Suspects
    Free DIY Tour
    Tr A
    不容易系列之(3)―― LELE的RPG难题
    W3C标准冒泡、捕获机制
    JavaScript 浏览器事件解读
  • 原文地址:https://www.cnblogs.com/hapjin/p/4755766.html
Copyright © 2020-2023  润新知