zoukankan      html  css  js  c++  java
  • 动态规划的思想来求解字符串分割问题

    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     }
  • 相关阅读:
    cf1100 F. Ivan and Burgers
    cf 1033 D. Divisors
    LeetCode 17. 电话号码的字母组合
    LeetCode 491. 递增的子序列
    LeetCode 459.重复的子字符串
    LeetCode 504. 七进制数
    LeetCode 3.无重复字符的最长子串
    LeetCode 16.06. 最小差
    LeetCode 77. 组合
    LeetCode 611. 有效三角形个数
  • 原文地址:https://www.cnblogs.com/hapjin/p/4755766.html
Copyright © 2011-2022 走看看