【过时】2021/1/29 最长公共子序列

最长公共子序列

http://www.nowcoder.com/questionTerminal/6d29638c85bb4ffd80c020fe244baf11

题目描述

给定两个字符串str1和str2,输出连个字符串的最长公共子序列。如过最长公共子序列为空,则输出-1。

示例

输入

"1A2C3D4B56","B1D23CA45B6A"

返回值

"123456"

说明

"123456"和“12C4B6”都是最长公共子序列,任意输出一个。

解题思路

相信做完《NC91 最长递增子序列》的胖友们会很容易看懂解题思路,接下来可能会讲的有点抽象,偶尽量图解,没做过的胖友建议先去试一试。(虽然NC91也有亿点抽象)

  1. 动态规划问题中的老办法,就是看输入值,如果有两个或以上的输入值,那就首先考虑用二维数组对付他,在很多题目都很有效,我们这里就借用上面的例子来演示。二维数组 dp 的纵轴 i 设为 str1,横轴 j 设为 str2,dp 中的元素表示 str1 第 i 个字符与 str2 第 j 个字符一致。
    图片说明
    圆圈表示 str1 在这一行的 j 位置找到了和 str2 里一样的字符。
    为了看的更清楚一些,我们画几条线将它们连起来。
    图片说明
    我们可以看到 str1 中有一个字符 B,而 str2中有两个,这里我们为了方便看到,用粗线标出。
    这并不是我们想要的,我们想要的一一对应应该是按照顺序来的,尽管它有多个相同的字符,如下图。
    图片说明
    这才是我们要的一一对应的效果嘛。

  2. 假设我们已经从(1)中获得了“圈圈”标记好的二维数组了,接下来我们该给这些“圈圈”标上数字了。(如果胖友们看不懂为什么要这样做,请先坚持下去,跟着思路走,最后解释清楚再倒回来看)
    标数字的准则是:纵轴上与自己上面的圈圈比较,在横轴则比较自己圈圈的位置和其他圈圈的位置,自己右邻圈圈的值赋给自己,如果自己就是最右边的圈圈,那就从左边那边拿最大值再 +1。

    图1
    图片说明

图2
图片说明

图3
图片说明

图4
图片说明

图5
图片说明

上面的图请胖友们自己在纸上画一画,顺序从上到下,规则是当前画到这一行的圈圈和上一行的圈圈作比较,如果当前行的圈圈在上一行圈圈的右边,那就在上面所有的圈圈里找一个最大值再 +1,否则在上面所有圈圈中无视找一个在右边离自己最近(无视纵轴深度)的圈圈复制值。

  1. 现在我们已经标完号了。现在我们从右往左,从下到上,标号从大到小获取字符吧!
    图片说明
    获得对应的值 [1, 2, 3, 4, 5, 6],也就是上面示例给出的返回值。至此我们算出了答案。

  2. 抛开上面的操作,我们整理一下我们刚才到底做了什么。

    • 首先,我们建立了一个用于标号的数组 temp。在遍历由 str1 和 str2 组成的二维数组 arr 时,当前遍历到的字符 arr[i][j] 的横坐标 j 需要存储进 temp,如果 j 比 temp 中最后一个元素值大,那么就直接将 j 存到 temp 最后。如果 j 比 temp 最后一个元素值小,那么就从 temp 中找到第一个比 j 大的值,然后用 j 替换它。j 所存放在 temp 的位置就是标号,将标号存放在 nums 数组的 nums[j] 中,便于最后执行像上面的操作,从右到左,从下到上,标号从大到小进行遍历。
    • 然后我们思考一下为什么要这么存放?其实 j 是 str2 的索引,只要将标号存放在 nums[j],就可以根据 j 获得 str2 在 j 中的字符及其对应的标号。
    • 为何这样就可以标号?其实胖友们只要自己动手画一下 temp 数组的存储过程就可以发现,二维数组 arr 的每次遍历其实都是动态规划中的子问题,每遍历一次子问题就扩大一次,当前遍历到的“圈圈”其实是当前子问题中,如果算上自己这个“圈圈”,那么公共子串长度是多少。
  3. 最后,要记得元素“一一对应”,方法有很多,我这里用的是 Map 记录重复元素的个数来实现一一对应。

Java代码实现

!!!思路是对的但代码错了,别看,我要脸!!!
@Deprecated

public class Solution {
    public String LCS (String s1, String s2) {
        // 为了方便阅读,这里把“无公共子序列则返回-1”的代码删掉
        int rowLen = s1.length();
        int colLen = s2.length();
        // 字典
        int[] nums = new int[rowLen];
        // 临时存储便于标号
        int[] temp = new int[colLen + 1];
        int tempIndex = 0;
        // 记录重复字符个数
        HashMap<Character, Integer> map = new HashMap<Character, Integer>(colLen);

        for (int i = 0; i < rowLen; ++i) {
            if (i==7) {
                System.out.println();
            }
            int charNum = 0;
            if (map.containsKey(s1.charAt(i))) {
                charNum = map.get(s1.charAt(i));
            }
            for (int j = 0; j < colLen; ++j) {
                char currentChar = s2.charAt(j);
                if (s1.charAt(i) == currentChar) {
                    // 去重
                    if (charNum > 0) {
                        --charNum;
                        continue;
                    }
                    if (j > temp[tempIndex]) {
                        ++tempIndex;
                        temp[tempIndex] = j;
                        nums[i] = tempIndex;
                        map.put(currentChar, charNum + 1);
                    } else {
                        int index = findFirstGreater(temp, tempIndex, j);
                        temp[index] = j;
                        nums[i] = index;
                    }
                    break;
                }
            }
        }

        char[] res = new char[tempIndex];
        for(int i = nums.length - 1; i > 0; --i) {
            if (nums[i] == tempIndex) {
                res[tempIndex - 1] = s1.charAt(i);
                --tempIndex;
            }
        }
        return new String(res);
    }

    private int findFirstGreater(int[] temp, int len, int source) {
        // 找第一个比 source 大的数
        int left = 1, right = len;
        while (left <= right) {
            int mid = (left + right) >> 1;
            if (source <= temp[mid]) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}
全部评论

相关推荐

点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务