31

漫画:最长公共子序列

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw%3D%3D&%3Bmid=2247491916&%3Bidx=3&%3Bsn=f7a995a34c1c91511c5ed65ba5f1abd1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
vayURfv.png!mobileiI3eaee.png!mobile

ERrI7bV.png!mobile

题目:

给定两个字符串 str1 和 str2,返回这两个字符串的最长公共子序列的长度

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

J3yYzmQ.png!mobile

也就是说对于以下两个字符串 str1 和 str2,其最长公共子串为 「acg」。

UrIvuuq.png!mobile

Rrmemaj.png!mobile

Mj2Qjiy.png!mobile

E7jeU3N.png!mobile

阿宝的想法

dp 是个二维数组,即 dp[i][j], 表示对于子串 str1[0..i] 与子串 str2[0..j], 它们的最长公共子序列长度为 dp[i][j], 这样的话根据定义, dp[str1.length-1][str2.length-1] 即为所求的解。

juue6zn.png!mobile

UVrIrin.png!mobile

阿宝画的状态转移表:

6va6ziu.png!mobile

ziER3mq.png!mobile

EfuyAzF.png!mobile

Yz6FVjy.png!mobile

  1. 当两个字符串 i,j 索引对应的字符相等时,如下图示

qAVrU3f.png!mobile

显然 dp[i][j] = dp[i-1][j-1] +1 , 1 代表 i 和 j 指向的字符相等,dp[i-1][j-1] 代表除此相同字符外的 i,j 索引 之前字符串的公共子序列。

2. 当两个字符串 i,j 索引对应的字符不相等时

qAVrU3f.png!mobile

此时 dp[i][j] 值可能为 dp[i-1][j] 或 dp[i][j-1], dp[i-1][j] 怎么理解,既然 i 与 j 指向的字符不等,那只要丢弃 i 字符,求  str1[0..i-1] 与 str2[0..j] 的最长公共子序列即可,即 dp[i-1][j], 同理对于dp[i][j-1],即为丢弃 j ,求 str1[0..i] 与 str2[0..j-1] 的最长公共子序列

3YJ7Rr3.png!mobilezM73YfU.png!mobile

既然 dp[i][j] 有可能等于这两个值,那么显然应该取这两者的较大值, 

dp[i][j] = max(dp[i-1][j], dp[i][j-1]) 。综上可知状态状态方程如下:

i6RRRni.png!mobile

ZBRby2Z.png!mobile

VfiAn2a.png!mobile

阿宝的想法:

空字符串与任何字符串的最长公共子序列都为 0,所以 dp[0][i], dp[j][0] 都为 0(i

为 0 到 str1 的长度, j 为 0 到 str2 的长度),如下图蓝色部分即为 base case。

byQBfqF.png!mobile

63iQFfJ.png!mobile

代码如下

public class Solution {
    public static int getLCS(char[] x, char[] y) {
        // base case,以下 dp 中的每个元素默认值都为 0。
        int dp[][] = new int[x.length][y.length];
        for (int i = 1; i < x.length; i++) {
            for (int j = 1; j < y.length; j++) {
                // 以下逻辑为状态转移方程
                if (x[i] == y[j]) {
                    dp[i][j] = dp[i-1][j-1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[x.length-1][y.length-1];
    }

    public static void main(String[] args) {
        char[] x =  {' ', 'a', 'b', 'c', 'e', 'f', 'g'};
        char[] y =  {' ', 'a', 'c', 'd', 'g'};
        int lcs = getLCS(x, y);
        System.out.printf("lcs = " + lcs);
    }
}

3i6zI3r.png!mobile

2QN3Eni.png!mobile

AveE7vb.png!mobile

ARJbaq3.png!mobile

总结

对于动态规划题型,其实套路大体相似,无非就是求出 dp 方程,再自下而上的求解,对于字符串类的动态规划题型,定义好可以先画出状态转移表,然后再据此找出状态转移方程,状态转移方程的推导有一定的技巧,根据状态(比如文中 i 和 j 对应字符是否相等)可能会有不同的情况,可以多考虑下对应的字符选或不选对应的 dp 是啥,据此推导  dp 会容易一些

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

eEjEFnz.jpg!mobile

RvIVFnB.png!mobile

JRRzuy.png!mobile

长按订阅更多精彩▼

rEFzyej.jpg!mobile

如有收获,点个在看,诚挚感谢


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK