【过时】2021/1/29 最长公共子序列
最长公共子序列
http://www.nowcoder.com/questionTerminal/6d29638c85bb4ffd80c020fe244baf11
题目描述
给定两个字符串str1和str2,输出连个字符串的最长公共子序列。如过最长公共子序列为空,则输出-1。
示例
输入
"1A2C3D4B56","B1D23CA45B6A"
返回值
"123456"
说明
"123456"和“12C4B6”都是最长公共子序列,任意输出一个。
解题思路
相信做完《NC91 最长递增子序列》的胖友们会很容易看懂解题思路,接下来可能会讲的有点抽象,偶尽量图解,没做过的胖友建议先去试一试。(虽然NC91也有亿点抽象)
动态规划问题中的老办法,就是看输入值,如果有两个或以上的输入值,那就首先考虑用二维数组对付他,在很多题目都很有效,我们这里就借用上面的例子来演示。二维数组 dp 的纵轴 i 设为 str1,横轴 j 设为 str2,dp 中的元素表示 str1 第 i 个字符与 str2 第 j 个字符一致。
圆圈表示 str1 在这一行的 j 位置找到了和 str2 里一样的字符。
为了看的更清楚一些,我们画几条线将它们连起来。
我们可以看到 str1 中有一个字符 B,而 str2中有两个,这里我们为了方便看到,用粗线标出。
这并不是我们想要的,我们想要的一一对应应该是按照顺序来的,尽管它有多个相同的字符,如下图。
这才是我们要的一一对应的效果嘛。假设我们已经从(1)中获得了“圈圈”标记好的二维数组了,接下来我们该给这些“圈圈”标上数字了。(如果胖友们看不懂为什么要这样做,请先坚持下去,跟着思路走,最后解释清楚再倒回来看)
标数字的准则是:纵轴上与自己上面的圈圈比较,在横轴则比较自己圈圈的位置和其他圈圈的位置,自己右邻圈圈的值赋给自己,如果自己就是最右边的圈圈,那就从左边那边拿最大值再 +1。图1
图2
![]()
图3
![]()
图4
![]()
图5
![]()
上面的图请胖友们自己在纸上画一画,顺序从上到下,规则是当前画到这一行的圈圈和上一行的圈圈作比较,如果当前行的圈圈在上一行圈圈的右边,那就在上面所有的圈圈里找一个最大值再 +1,否则在上面所有圈圈中无视找一个在右边离自己最近(无视纵轴深度)的圈圈复制值。
现在我们已经标完号了。现在我们从右往左,从下到上,标号从大到小获取字符吧!
获得对应的值 [1, 2, 3, 4, 5, 6],也就是上面示例给出的返回值。至此我们算出了答案。抛开上面的操作,我们整理一下我们刚才到底做了什么。
- 首先,我们建立了一个用于标号的数组 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 的每次遍历其实都是动态规划中的子问题,每遍历一次子问题就扩大一次,当前遍历到的“圈圈”其实是当前子问题中,如果算上自己这个“圈圈”,那么公共子串长度是多少。
最后,要记得元素“一一对应”,方法有很多,我这里用的是 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;
}
}
查看19道真题和解析