动态规划,它来了(三)

joytrian
发布于 2022-7-7 17:03
浏览
0收藏

 

最长公共子序列
最长公共子序列也成为LCS.出现频率非常高!

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

拿b c d d e和 a c e e d e举例,其的公共子串为c d e。如果使用暴力,复杂度太高会直接超时,就需要使用动态规划。两个字符串匹配,我们设立二维dp[][]数组,dp[i][j]表示text1串第i个结尾,text2串第j个结尾的最长公共子串的长度。

这里核心就是要搞懂状态转移,分析dp[i][j]的转换情况,当到达i,j时候:

如果text1[i]==text2[j],因为两个元素都在最末尾的位置,所以一定可以匹配成功,换句话说,这个位置的邻居dp值不可能大于他(最多相等)。所以这个时候就是dp[i][j]=dp[i-1][j-1] +1;

动态规划,它来了(三)-鸿蒙开发者社区

如果text1[i]!=text2[j],就有两种可能性,我们知道的邻居有dp[i-1][j],dp[i][j-1],很多人还会想到dp[i-1][j-1]这个一定比前两个小于等于,因为就是前面两个子范围嘛!所以这时就相当于末尾匹配不成,就要看看邻居能匹配的最大值啦,此时dp[i][j]=max(dp[i][j-1],dp[i-1][j])。

动态规划,它来了(三)-鸿蒙开发者社区

所以整个状态转移方程为:

dp[i][j] = dp[i-1][j-1] + 1            //text1[i]==text2[j]时
dp[i][j] = max(dp[i][j-1],dp[i-1][j])  //text1[i]!=text2[j]时

实现代码为:

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        char ch1[]=text1.toCharArray();
        char ch2[]=text2.toCharArray();
        int dp[][]=new int[ch1.length+1][ch2.length+1];
        for(int i=0;i<ch1.length;i++)
        {
            for(int j=0;j<ch2.length;j++)
            {
                if(ch1[i]==ch2[j])
                {
                    dp[i+1][j+1]=dp[i][j]+1;
                }
                else
                    dp[i+1][j+1]=Math.max(dp[i][j+1],dp[i+1][j]);

            }
        }
        return dp[ch1.length][ch2.length];
    }
}

最长公共子串
给定两个字符串str1和str2,输出两个字符串的最长公共子串。

例如 abceef 和a2b2cee3f的最长公共子串就是cee。公共子串是两个串中最长连续的相同部分。

如何分析呢? 和上面最长公共子序列的分析方式相似,要进行动态规划匹配,并且逻辑上处理更简单,只要当前i,j不匹配那么dp值就为0,如果可以匹配那么就变成dp[i-1][j-1] + 1

核心的状态转移方程为:

dp[i][j] = dp[i-1][j-1] + 1            //text1[i]==text2[j]时
dp[i][j] = 0  //text1[i]!=text2[j]时

这里代码和上面很相似就不写啦,但是有个问题有的会让你输出最长字符串之类,你要记得用一些变量存储值。

不同子序列
不同子序列也会出现,并且有些难度,前面这篇不同子序列问题分析讲的大家可以看看。

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

字符串的一个 子序列 是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)

示例 :

输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下图所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
(上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^

分析:
这个问题其实就是上面有几个pat的变形拓展,其基本思想其实是一致的,上面那题问的是有几个pat,固定、且很短。但这里面t串的长度不固定,所以处理上就要使用数组来处理而不能直接if else。

这题的思路肯定也是动态规划dp了,dp[j]的意思就是t串中[0,j-1]长字符在s中能够匹配的数量(当然这个值从前往后是动态变化的),数组大小为dp[t.length+1]。在遍历s串的每一个元素都要和t串中所有元素进行对比看看是否相等,如果s串枚举到的这个串和t串中的第j个相等。那么dp[j+1]+=dp[j]。你可能会问为啥是dp[j+1],因为第一个元素匹配到需要将数量+1,而这里为了避免这样的判断我们将dp[0]=1,这样t串的每个元素都能正常的操作。

但是有一点需要注意的就是在遍历s串中第i个字母的时候,遍历t串比较不能从左向右而必须从右向左。因为在遍历s串的第i个字符在枚举dp数组时候要求此刻数据是相对静止的叠加(即同一层次不能产生影响),而从左往右进行遇到相同字符会对后面的值产生影响。区别的话可以参考下图这个例子:

动态规划,它来了(三)-鸿蒙开发者社区

实现的代码为:

class Solution {
    public int numDistinct(String s, String t) {
      char s1[]=s.toCharArray();
      char t1[]=t.toCharArray();
      int dp[]=new int[t1.length+1];
      dp[0]=1;//用来叠加

      for(int i=0;i<s1.length;i++)
      {
        for(int j=t1.length-1;j>=0;j--)
        {
          if(t1[j]==s1[i])
          {
            dp[j+1]+=dp[j];
          }
        }
      }
      return dp[t1.length];
    }
}

 

结语
至此,简单的动态规划算是分享完了。

大部分简单动态规划还是有套路的,你看到一些数组问题、字符串问题很有可能就暗藏动态规划。动态规划的套路跟递归有点点相似,主要是找到状态转移方程,有时候考虑问题不能一步想的太多(想太多可能就把自己绕进去了),而动态规划就是要大家对数值上下转换计算需要了解其中关系。

对于复杂dp问题或者很多套一层壳确实很难看出来,但是掌握上面的常见dp问题和背包问题,就可以解决大部分动态规划问题啦(毕竟咱们不是搞竞赛遇到的还是偏简单或者中等难度的)。

 

文章转自公众号:bigsai

标签
已于2022-7-7 17:03:48修改
收藏
回复
举报
回复
    相关推荐