4.动规3



回文串分割(Palindrome Partitioning)

给出一个字符串s,分割s使得分割出的每一个子串都是回文串计算将字符串s分割成回文分割结果的最小切割数例如:给定字符串s=“aab”,返回1,因为回文分割结果[“aa”,“b”]是切割一次生成的。

“aab”

问题

将字符串s分割成回文分割结果的最小切割数

状态

字符串前i个字符的最小分割分割的段数,也就是最小分割数+1

“aab”

F[0] = 1–>“a”

F[1] = is(str[j, 1])?(j>0?F[j-1]+1:1) : F[1-1]+1 = 1–>“a” “a” || “aa” (0<=j<=i)

F[2] = is(str[j, 2])?(j>0?F[j-1]+1:1) : F[2-1]+1 = 2–>“aa” “b” || “aab” (0<=j<=i)

“abbab”

F[0] = 1–>“a”

F[1] = is(str[j, 1])?(j>0?F[j-1]+1:1) : F[1-1]+1 = 2–>“ab” “b”(0<=j<=i)

F[2] = is(str[j, 2])?(j>0?F[j-1]+1:1) : F[2-1]+1 = 2–>“abb” “bb” “b” (0<=j<=i)

F[3] = is(str[j, 3])?(j>0?F[j-1]+1:1) : F[3-1]+1 = 1–>“abba” “bba” “ba” “a” (0<=j<=i)

F[4] = is(str[j, 4])?(j>0?F[j-1]+1:1) : F[4-1]+1 = 3–>“abbab” “bbab” “bab” “ab” “b” (0<=j<=i)

F[4]漏掉了这些状态:“abbab” “abba” “abb” “ab” “a” (0<=j<=i)

F[4-1]+1就是"abba"+"b"分割方式

上面状态不完美的原因是忽略了部分子状态,因此重新找状态:

F[4]: if is(str[0, j]) F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);break; (0<=j<=i)

(j>0?F[j-1]+1:1)就是"abbab" “bbab” “bab” “ab” "b"情况

(i>0?F[i-1]+1:1)就是"abbab" “abba” “abb” “ab” "a"情况

转移方程

not prefect:F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)

prefect:F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);

初始状态

F[0] = 1

返回

F[s.length - 1] - 1

代码

不完美的状态转移方程:17/20

Java

import java.util.*;


public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    public int minCut (String s) {
        int len = s.length();
        int F[] = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:1): F[i-1]+1;
                if(isPalindrom(s, j, i))break;
            }
        }
        return F[len-1]-1;
    }
    boolean isPalindrom(String s, int left, int right){
        while(left < right){
            if(s.charAt(left++) != s.charAt(right--))
                return false;
        }
        return true;
    }
}

C++

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        // write code here
        //F[i] = is(str[j, i])?F[i-1]:F[i-1]+1  (0<=j<=i)
        int len = s.length();
        int* F = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:1): F[i-1]+1;
                if(isPalindrom(s, j, i))break;
            }
        }
        return F[len-1]-1;
    }
    bool isPalindrom(string s, int left, int right){
        while(left < right){
            if(s[left++] != s[right--])
                return false;
        }
        return true;
    }
};
完美转移方程:通过所有测试用例

Java

import java.util.*;


public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    public int minCut (String s) {
        int len = s.length();
        int F[] = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:1) > F[i-1]+1? F[i-1]+1 : (j>0?F[j-1]+1:1)
                if(isPalindrom(s, j, i)){
                    F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);
                    break;
                }
            }
        }
        return F[len-1]-1;
    }
    boolean isPalindrom(String s, int left, int right){
        while(left < right){
            if(s.charAt(left++) != s.charAt(right--))
                return false;
        }
        return true;
    }
}

C++

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        // write code here
        //F[i] = is(str[j, i])?F[i-1]:F[i-1]+1  (0<=j<=i)
        int len = s.length();
        int* F = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);
                    break;
                }
            }
        }
        return F[len-1]-1;
    }
    bool isPalindrom(string s, int left, int right){
        while(left < right){
            if(s[left++] != s[right--])
                return false;
        }
        return true;
    }
};

虽然完美的解决了这道题

4.动规3_第1张图片

但对于极度扣性能的我来说,一定可以找到更舒服的状态和定义更完美的转移方程

状态

字符串前i个字符的最小分割分割次数

拿"abbab"来找状态

F[0] = 0 --> “a”

F[2] = 1 --> “a” “b” (“ab” | “a” “b”)

F[3] = 1 --> “a” “bb” (“abb” | “ab” “b” | “a” “b” “b” | “a” “bb” | “ab” “b”)

每个状态只需要关注两种方向的组合就行了

F[i-1]+1对应[0, i]的组合(0<=i<=s.len)
F[j-1]+1对应[j, i]的组合(j=0;j<=i;++j)

执行逻辑:

F[i] = if isPalindrom(str[j,i]) min((j>0?F[j-1]+1:1), (i>0?F[i-1]+1:1)) (j=0;j<=i;++j)(0<=i<=s.len)

最终状态转移方程:

if isPalindrom(str[j,i]) F[i] = min((j>0?F[j-1]+1:0), (i>0?F[i-1]+1:0)); (j=0;j<=i;++j)(0<=i<=s.len)

返回

F[s.len-1]

代码

Java

import java.util.*;


public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    public int minCut (String s) {
        int len = s.length();
        int F[] = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:0), (i>0?F[i-1]+1:0));
                    break;
                }
            }
        }
        return F[len-1];
    }
    boolean isPalindrom(String s, int left, int right){
        while(left < right){
            if(s.charAt(left++) != s.charAt(right--))
                return false;
        }
        return true;
    }
}

C++

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        // write code here
        //F[i] = is(str[j, i])?F[i-1]:F[i-1]+1  (0<=j<=i)
        int len = s.length();
        int* F = new int[len];
        //F[0] = 1
        //F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1  (0<=j<=i)
        for(int i=0; i0?F[j-1]+1:0), (i>0?F[i-1]+1:0));
                    break;
                }
            }
        }
        return F[len-1];
    }
    bool isPalindrom(string s, int left, int right){
        while(left < right){
            if(s[left++] != s[right--])
                return false;
        }
        return true;
    }
};

C++ string lenght()和size()成员方法都可以求字符串长度

4.动规3_第2张图片

内存和运行时间都有所提高

再看另外一种思路

问题

将字符串s分割成回文分割结果的最小切割数

状态

字符串s前i个字符分割成回文分割结果的最小切割数

看"aab"字符串

F[1] = “a” --> 0

F[2] = “aa” --> 0

F[3] = “aab” F[2]+1 --> 1

F[3]可以用上F[2]的状态

看"aabaab"字符串

F[1] = “a” --> 0

F[2] = “aa” --> 0

F[3] = “aab”, F[1]|“ab”, F[2]|“b” --> 1

F[4] = “aaba”, F[1]|“aba”, F[2]|“ba”, F[3]|“a”

F[5] = “aabaa” --> 0

F[6] = “aabaab”, F[2]|“baab”, F[5]|“a” --> 1

看"abbab"字符串

F[1] = “a” --> 0

F[2] = “ab” --> 1

F[3] = “abb”, F[1]|“bb” 1, F[2]|“b” 2 --> 1

F[4] = “abba” --> 0

F[5] = “abbab”, F[4]|“b” --> 1

转移方程

F[i]: j

初始状态

全给最大值

F[i] = i-1 (i)

返回

F[n]

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        vector minc(s.length()+1);
        //F[i] = i-1
        for(int i=1; i<=s.length(); ++i)
            minc[i] = i-1;
        for(int i=2; i<=s.length(); ++i){
            //isPalindrom(str[1,i]) 前i个字符是回文
            if(isPalindrom(s, 0, i-1)){
                minc[i] = 0;
                continue;
            }
            //isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
            for(int j=1; j

F[0]给-1,就可以将j==0合并到循环里

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        vector minc(s.length()+1);
        //F[i] = i-1
        for(int i=0; i<=s.length(); ++i)
            minc[i] = i-1;
        //F[0]给-1,就可以将j==0合并到循环里
        for(int i=2; i<=s.length(); ++i){
            //isPalindrom(str[1,i]) 前i个字符是回文
            //isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
            for(int j=0; j

还是不太满意,因为判断一个字符串是不是回文时间复杂度是O(n)

继续对判断字符串是不是回文的函数进行动规优化–>整个字符串任意区间是否是回文结果保存在二维矩阵中,判断分割数的循环中再判断一个区间是不是回文时间复杂度就是O(1)了

问题

字符串区间[i, j]是否是回文串

状态

区间[i, j]是否是回文串

转移方程

F[i, j]: [j+1, j-1] && s[i] == s[j] (j>=i状态才有意义)

j

j-i == 1 return s[i] == s[j];

简单举个例子

F[3,4]就依赖于F[4,3]的状态,也就是当前的状态依赖于二维矩阵中左下角的状态,j>=i状态才有意义则二维矩阵从定义得到所有状态的结果为之都保持上三角矩阵,我们需要定义n^2的矩阵(n是字符串长度)

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    int minCut(string s) {
        vector> ispal(s.length(), vector(s.length(), 0));
        ispal[0][0] = 1;
        for(int i=s.length()-1; i>=0; --i){
            for(int j=i; j minc(s.length()+1);
        //F[i] = i-1
        for(int i=0; i<=s.length(); ++i)
            minc[i] = i-1;
        //F[0]给-1,就可以将j==0合并到循环里
        for(int i=2; i<=s.length(); ++i){
            //isPalindrom(str[1,i]) 前i个字符是回文
            //isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
            for(int j=0; j
4.动规3_第3张图片



编辑距离(Edit Distance)

给定两个单词word1和word2,请计算将word1转换为word2至少需要多少步操作。
你可以对一个单词执行以下3种操作:

( a) 在单词中插入一个字符

( b) 删除单词中的一个字符

( c) 替换单词中的一个字符

问题

word1转成word2的最小操作次数(编辑数)

分析

"ab"转成"bc"有三种办法:

(1).删除’a’–>“b”;插入’c’–>“bc”

(2).‘a’替换成’b’–>“bb”;‘b’替换成’c’–>“bc”

(3).‘a’替换成’’–>“b”;插入’c’–>“bc”

子问题

word1前i个字符转成word2的最小操作次数

状态

F(i, j): word1前i个字符转成word2的前j个字符的最小操作次数

“ab”(word1)转成"bc"(word2)
F(i): word1前i个字符转成word2的最小操作次数

F(i):F(i-1)+1  (删除第i个字符)

F(1):最好的情况是strlen(word2)-1,最差是strlen(word2)

这个状态只考虑了删除,重新考虑:...如下:

当前的状态要考虑能够使用到前面已知的状态,当前word1前i个字符转成word2的前j个字符的状态可以通过:word1的前i-1个字符转成word2的前j-1个字符的状态加可能需要的一次替换1得到; word1的前i个字符转成word2的前j-1个字符的状态加一次插入2得到; word1的前i-1个字符转成word2的前j个字符的状态加一次删除3得到; 三种前面已知的状态得到,因此当前状态word1前i个字符转成word2的前j个字符的值就取自三者中最小的

F(1, 1): “a” --> “b” 发生一次替换 = 1

F(2, 1): “ab” --> “b” F(1, 1)+删除’b’ = 2

F(1, 2): “a” --> “bc” F(1, 1)+插入’c’ = 2

F(2, 2): “ab” --> “bc” min{F(1, 1):“bb” + ‘b’替换成’c’ = 2; F(2, 1):“b” + 插入’c’ = 3; F(1, 2):“bcb” + 'b’删除 = 3} = 2

F(2, 2)可以通过:F(1, 1)加一次替换; F(2, 1)加一次插入; F(1, 2)加一次删除得到,取三者最小值即可

具体步骤如下:

状态有两个参数,很明显所有的状态需要二维矩阵才能表示完

下标 j 0 1 2
i s[i/j] “” b c
0 “” 状态:F(0, 0)
始末:""–>""
操作:无
编辑数:0
状态:F(0, 1)
始末:""–>“b”
操作:插入"b"
编辑数:1
状态:F(0, 2)
始末:""–>“bc”
操作:插入"b";插入"b"
编辑数:2
1 a 状态:F(1, 0)
始末:“a”–>""
操作:删除"a"
编辑数:1
状态:F(1, 1)
始末:“a”–>“b”
操作:
 替换:“a”–>“b” = F(0, 0)+1
 插入:""-b->“b” = F(1, 0)+1
 删除:“ba”-a->“b” = F(0, 1)+1
编辑数:min{F(0, 0), F(1, 0), F(0, 1)}+1 = 1
状态:F(1, 2)
始末:“a”–>“bc”
操作:
 替换:“ba”–>“bc” = F(0, 1)+1
 插入:“b”-c->“bc” = F(1, 1)+1
 删除:“bca”-a->“bc” = F(0, 2)+1
编辑数:min{F(0, 1), F(1, 1), F(0, 2)}+1 = 2
2 b 状态:F(2, 0)
始末:“ab”–>""
操作:删除"a";删除"b"
编辑数:2
状态:F(2, 1)
始末:“ab”–>“b”
操作:
 替换:“b”–>“b” = F(1, 0)+04
 插入:“b”-c->“bc” = F(2, 0)+1
 删除:“bca”-a->“bc” = F(1, 1)+1
编辑数:min{F(1, 0), F(2, 0), F(1, 1)}+1 = 1
状态:F(2, 2)
始末:“ab”–>“bc”
操作:
 替换:“bb”–>“b” = F(1, 1)+1
 插入:“b”-c->“bc” = F(2, 1)+1
 删除:“bcb”-b->“bc” = F(1, 2)+1
编辑数:min{F(1, 1), F(2, 1), F(1, 1)}+1 = 1
F(i, j)利用三个已知状态的状态转移方向如下:

4.动规3_第4张图片

F(i, j)转移方程提取

F(i, j)可以通过

  • 替换:F(i-1, j-1) + (word1[i]==word2[j]?0:1)
  • 插入:F(i, j-1) + 1
  • 删除:F(i-1, j) + 1

字符串string下标是从0开始的,因此判断word1的第i个和word2的第j个字符是否相同应该是:word1[i-1]==word2[j-1]

转移方程

F(i, j):

初始状态

F(0, 0) = 0

F(i, 0) = i (删除)

F(0, j) = j (插入)

返回

F(strlen(word1), strlen(word2))

需要注意的是word1,word2只要有一个是空字符串,就直接返回非空字符串的长度

代码

class Solution {
public:
    /**
     * 
     * @param word1 string字符串 
     * @param word2 string字符串 
     * @return int整型
     */
    int minDistance(string word1, string word2) {
        // write code here
        int word1_len = word1.size(); //i
        int word2_len = word2.size(); //j
        if(!word1_len) return word2_len;
        if(!word2_len) return word1_len;
        vector> mid_res(word1_len+1, vector(word2_len+1, 0));
        //F(i, 0) = i (删除)
        for(int i=1; i<=word1_len; ++i)
            mid_res[i][0] = i;
        // F(0, j) = j (插入)
        for(int j=1; j<=word2_len; ++j)
            mid_res[0][j] = j;
        for(int i=1; i<=word1_len; ++i){
            for(int j=1; j<=word2_len; ++j){
                mid_res[i][j] = min(mid_res[i-1][j-1]+(word1[i-1]==word2[j-1]?0:1), min(mid_res[i-1][j]+1, mid_res[i][j-1]+1));
            }
        }
        return mid_res[word1_len][word2_len];
    }
};
4.动规3_第5张图片



不同子序列(Distinct Subsequences)

给定两个字符串S和T,返回S子序列等于T的不同子序列个数有多少个?

字符串的子序列是由原来的字符串删除一些字符(也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)

例如:

S="nowcccoder", T = "nowccoder"

返回3

。。。。。。后续更新不同子序列(Distinct Subsequences)的解题思路和分析步骤,敬请关注+收藏


  1. word1的前i-1已经转成word2的前j-1个,则word1的第i个字符至少一次替换就可以得到word2的第j个字符从而word1转成word2,如果word1[i]==word2[j]则不用替换 ↩︎

  2. word1的前i个字符已经转成word2的前j-1个字符,则word1插入word2的第j个字符就可以得到word2 ↩︎

  3. word1的前i-1个字符已经转成word2的前j个字符,则word1删除word1的第i个字符就可以得到word2 ↩︎

  4. 条件判断word1[2]==word1[1] ↩︎

你可能感兴趣的:(动规如此简单)