算法:动态规划第三讲

第10题 回文串分割(Palindrome Partitioning)

题目链接

题目描述

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

解题思路

状态:
子状态:到第1,2,3,…,n个字符需要的最小分割数
F(i): 到第i个字符需要的最小分割数
状态递推:
F(i) = min{F(i), 1 + F(j)}, where j<i && j+1到i是回文串
上式表示如果从j+1到i判断为回文字符串,且已经知道从第1个字符
到第j个字符的最小切割数,那么只需要再切一次,就可以保证
1–>j, j+1–>i都为回文串。
初始化:
F(i) = i - 1
上式表示到第i个字符需要的最大分割数
比如单个字符只需要切0次,因为单子符都为回文串
2个字符最大需要1次,3个2次…
返回结果:
F(n)
示例;
在这里插入图片描述

代码实现

public class Solution {
  //判断是否回文串
  public boolean isPal(String s, int start, int end){
    while(start < end){
      if(s.charAt(start) != s.charAt(end))
        return false;
      ++start;
      --end;
   }
    return true;
 }
  public int minCut(String s) {
    int len = s.length();
    if(len == 0)
      return 0;
    int[] minCut = new int[len + 1];
    // F(i)初始化
// F(0)= -1,必要项,如果没有这一项,对于重叠字符串“aaaaa”会产生错误的结果
    for(int i = 0; i <= len; ++i){
      minCut[i] = i - 1;
   }
    for(int i = 1; i <= len; ++i){
      for(int j = 0; j < i; ++j){
        // F(i) = min{F(i), 1 + F(j)}, where j<i && j+1到i是回文串
// 从最长串判断,如果从第j+1到i为回文字符串
// 则再加一次分割,从1到j,j+1到i的字符就全部分成了回文字符串
        if(isPal(s, j, i - 1)){
          minCut[i] = Math.min(minCut[i], minCut[j] + 1);
       }
     }
   }
    return minCut[len];
 }
}
/*
上述方法两次循环时间复杂度是O(n^2),
判断回文串时间复杂度是O(n),
所以总时间复杂度为O(n^3)
对于过长的字符串,在OJ的时候会出现TLE(Time Limit Exceeded)
判断回文串的方法可以继续优化,使总体时间复杂度将为O(n^2)

优化代码(判断回文使用动态规划)

思路:
状态:
子状态:从第一个字符到第二个字符是不是回文串,第1-3,第2-5,…
F(i,j): 字符区间 [i,j] 是否为回文串
状态递推:
F(i,j): true->{s[i]==s[j] && F(i+1,j-1)} OR false
上式表示如果字符区间首尾字符相同且在去掉区间首尾字符后字符区间仍为回文串,
则原字符区间为回文串
从递推公式中可以看到第i处需要用到第i+1处的信息,所以i应该从字符串末尾遍历
初始化:
F(i,j) = false
返回结果:
矩阵F(n,n), 只更新一半值(i <= j),n^2 / 2

public class Solution {
        /**
         *
         * @param s string字符串
         * @return int整型
         */
        public int minCut (String s) {
            // write code here
            if (s.length() == 0) {
                return 0;
            }

            boolean[][] isPal = new boolean[s.length()][s.length()];
            for (int i = s.length()-1; i >=0 ; i--) {
                for (int j = i; j < s.length() ; j++) {
                    if (j == i) {
                        isPal[i][j] = true;
                    }
                    else if (j == i+1) {
                        isPal[i][j] = (s.charAt(i) == s.charAt(j));
                    }
                    else {
                        isPal[i][j] = ((s.charAt(i) == s.charAt(j)) && isPal[i + 1][j - 1]);
                    }

                }

            }

            int[] minCut = new int[s.length() + 1];
            // 初始化 i - 1
            for (int i = 0; i < s.length() + 1 ; i++) {
                minCut[i] = i - 1;
            }
            for (int i = 1; i <= s.length(); i++) {
                // 判断他整体是不是回文 ,是的话就置为 0;
                for (int j = 0; j < i ; j++) {
                    // 判断 j, i-1
                    if (isPal[j][i-1])
                    {
                        minCut[i] = Math.min(minCut[i], minCut[j]  + 1);
                    }
                }
            }
            return minCut[s.length()];
        }

        private boolean isPal(String s, int start, int end) {
            for (int i = start ; i < end ; i++) {
                if (s.charAt(i) != s.charAt(end))  {
                    return false;
                }
                end--;

            }
            return true;
        }
    }
/*
总结:
 简单的动态规划问题,状态,状态递推和状态初始化都比较直观
 对于复杂的动态规划问题,状态,状态递推和状态初始化都比较隐含,需要仔细推敲
 尤其是状态递推可能需要额外的辅助判断条件才能达成。

第11题 编辑距离(Edit Distance)

编辑距离:是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。
题目链接

题目描述

给定两个单词word1和word2,请计算将word1转换为word2至少需要多少步操作。
你可以对一个单词执行以下3种操作:
a)在单词中插入一个字符
b)删除单词中的一个字符
c)替换单词中的一个字符

解题思路

状态:
子状态:word1的前1,2,3,…m个字符转换成word2的前1,2,3,…n个字符需要的编辑距离
F(i,j):word1的前i个字符于word2的前j个字符的编辑距离
状态递推:
F(i,j) = min { F(i-1,j)+1, F(i,j-1) +1, F(i-1,j-1) +(w1[i]==w2[j]?0:1) }
上式表示从删除,增加和替换操作中选择一个最小操作数
F(i-1,j): w1[1,…,i-1]于w2[1,…,j]的编辑距离,删除w1[i]的字符—>F(i,j)
F(i,j-1): w1[1,…,i]于w2[1,…,j-1]的编辑距离,增加一个字符—>F(i,j)
F(i-1,j-1): w1[1,…,i-1]于w2[1,…,j-1]的编辑距离,如果w1[i]与w2[j]相同,
不做任何操作,编辑距离不变,如果w1[i]与w2[j]不同,替换w1[i]的字符为w2[j]—>F(i,j)
初始化:
初始化一定要是确定的值,如果这里不加入空串,初始值无法确定
F(i,0) = i :word与空串的编辑距离,删除操作
F(0,i) = i :空串与word的编辑距离,增加操作
返回结果:F(m,n)
在这里插入图片描述

代码实现

public class Solution {
        /**
         *
         * @param word1 string字符串
         * @param word2 string字符串
         * @return int整型
         */
        public int minDistance (String word1, String word2) {
            // write code here
            int len1 = word1.length();
            int len2 = word2.length();
            int[][] minD = new  int[len1 +  1][len2 + 1];
            // 一定要注意初始化 行和列
            for (int i = 0; i <= len1 ; i++) {
                minD[i][0] = i;
            }
            for (int i = 0; i <= len2 ; i++) {
                minD[0][i] = i;
            }
            for (int i = 1; i <= len1 ; i++) {
                for (int j = 1; j <= len2 ; j++) {
                    // 插入 删除
                    minD[i][j] = Math.min(minD[i][j-1], minD[i-1][j]) + 1;
                    // 判断word1的第i个字符是否与word2的第j个字符相等
                    if (word1.charAt(i-1) == word2.charAt(j-1)) {
                        minD[i][j] = Math.min(minD[i][j], minD[i-1][j-1]);
                    }
                    else {
                        minD[i][j] = Math.min(minD[i][j], minD[i-1][j-1]  + 1);
                    }
                }
            }
            return minD[len1][len2];
        }
    }

第12题 不同子序列(Distinct Subsequences)

题目链接

题目描述

给定两个字符串S和T,返回S子序列等于T的不同子序列个数有多少个?
字符串的子序列是由原来的字符串删除一些字符(也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)
例如:
S=“nowcccoder”, T = “nowccoder”
返回3

解题思路

状态:
子状态:由S的前1,2,…,m个字符组成的子串与T的前1,2,…,n个字符相同的个数
F(i,j): S[1:i]中的子串与T[1:j]相同的个数
状态递推:
在F(i,j)处需要考虑S[i] = T[j] 和 S[i] != T[j]两种情况
当S[i] = T[j]:
1>: 让S[i]匹配T[j],则
F(i,j) = F(i-1,j-1)
2>: 让S[i]不匹配T[j],则问题就变为S[1:i-1]中的子串与T[1:j]相同的个数,则
F(i,j) = F(i-1,j)
故,S[i] = T[j]时,F(i,j) = F(i-1,j-1) + F(i-1,j)
当S[i] != T[j]:
问题退化为S[1:i-1]中的子串与T[1:j]相同的个数
故,S[i] != T[j]时,F(i,j) = F(i-1,j)
初始化:引入空串进行初始化
F(i,0) = 1 —> S的子串与空串相同的个数,只有空串与空串相同
返回结果:
F(m,n)

代码实现

public class Solution {
        /**
         *
         * @param S string字符串
         * @param T string字符串
         * @return int整型
         */
        public int numDistinct (String S, String T) {
            // write code here
            int len1 = S.length();
            int len2 = T.length();
            int[][] numD = new int[len1 + 1][len2 + 1];
            // 初始化
            for (int i = 0; i <=len1 ; i++) {
                numD[i][0] = 1;
            }
            for (int i = 0; i <=len2 ; i++) {
                numD[0][i] = 0;
            }
            for (int i = 1; i <= len1 ; i++) {
                for (int j = 1; j <= len2 ; j++) {
                    if (S.charAt(i - 1)  == T.charAt(j - 1)) {
                        numD[i][j] = numD[i - 1][j - 1] + numD[i - 1][j];
                    }
                    else {
                        numD[i][j] = numD[i - 1][j];
                    }
                }
            }
            return  numD[len1][len2];
        }
    }

优化代码

/*
此题也可优化空间复杂度为O(n)
f[i][j] 只和 f[i - 1][j], f[i - 1][j - 1]有关
类似于背包问题,可以用一维数组保存上一行的结果,每次从最后一列更新元素值
*/
public class Solution {
  public int numDistinct(String S, String T) {
    int sLen = S.length();
    int tLen = T.length();
    int[] numDis = new int[tLen + 1];
    numDis[0]= 1;
    for(int i = 1; i <= tLen; ++i){
      numDis[i] = 0;
   }
    for(int i = 1; i <= sLen; ++i){
      for(int j = tLen; j > 0; --j){
        if(S.charAt(i - 1) == T.charAt(j - 1)){
          numDis[j] = numDis[j] + numDis[j - 1];
       }
     }
   }
    return numDis[tLen];
 }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值