刚拿到这个题目,脑子的第一反应是,这是一个三重循环的题目。
方法一:
利用两重循环,拿string1的首字母与string2的每一个字符进行比较,如果相同则获取到该两个字符的index,并比较之后的字符直到出现不一样的为止。
C#代码:
1 public static string LCSMethod(string s1, string s2)
2 {
3 int MaxLength=0;
4 int StartIndex = 0;
5 for (int i = 0; i < s1.Length; i++)
6 {
7 for (int j = 0; j < s2.Length; j++)
8 {
9 if (s1[i]==s2[j])
10 {
11 int k = 0;
12 while (s1[i+k]==s2[i+k])
13 {
14 k++;
15 if (i+k==s1.Length||i+k==s2.Length)
16 {
17 break;
18 }
19 }
20 if (k>MaxLength)
21 {
22 MaxLength = k;
23 StartIndex = i;
24 }
25 }
26
27 }
28 }
29 return s1.Substring(StartIndex, MaxLength);
30 }
这样的复杂度是O(n^3)
经过在网上看到许多人都提到用矩阵对角的思想解决,这样时间复杂度可以达到O(n^2),但是也是牺牲了空间的情况。需要声明一个string1.length*string2.length的int型数组。
方法二:
用一个矩阵来记录两个字符串中所有位置的两个字符之间的匹配情况,若是匹配则为1,否则为0。然后求出对角线最长的1序列,其对应的位置就是最长匹配子串的位置.
下面是字符串21232523311324和字符串312123223445的匹配矩阵,前者为X方向的,后者为Y方向的。不难找到,红色部分是最长的匹配子串。通过查找位置我们得到最长的匹配子串为:21232
0 0 0 1 0 0 0 1 1 0 0 1 0 0 0
0 1 0 0 0 0 0 0 0 1 1 0 0 0 0
1 0 1 0 1 0 1 0 0 0 0 0 1 0 0
0 1 0 0 0 0 0 0 0 1 1 0 0 0 0
1 0 1 0 1 0 1 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 1 1 0 0 1 0 0 0
1 0 1 0 1 0 1 0 0 0 0 0 1 0 0
1 0 1 0 1 0 1 0 0 0 0 0 1 0 0
0 0 0 1 0 0 0 1 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
但是在0和1的矩阵中找最长的1对角线序列又要花去一定的时间。通过改进矩阵的生成方式和设置标记变量,可以省去这部分时间。下面是新的矩阵生成方式:
0 0 0 1 0 0 0 1 1 0 0 1 0 0 0
0 1 0 0 0 0 0 0 0 2 1 0 0 0 0
1 0 2 0 1 0 1 0 0 0 0 0 1 0 0
0 2 0 0 0 0 0 0 0 1 1 0 0 0 0
1 0 3 0 1 0 1 0 0 0 0 0 1 0 0
0 0 0 4 0 0 0 2 1 0 0 1 0 0 0
1 0 1 0 5 0 1 0 0 0 0 0 2 0 0
1 0 1 0 1 0 1 0 0 0 0 0 1 0 0
0 0 0 2 0 0 0 2 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
C#代码
1 //Longest common subsequence
2 public static string LCSMethod(string s1, string s2)
3 {
4 if (String.IsNullOrEmpty(s1) || String.IsNullOrEmpty(s2))
5 {
6 return null;
7 }
8 else if (s1 == s2)
9 {
10 return s1;
11 }
12
13 //定义最大子字符串长度为0;
14 int length = 0;
15 int end = 0;
16 //定义一个字符2维数组,默认都是0
17 int[,] a = new int[s1.Length, s2.Length];
18 for (int i = 0; i < s1.Length; i++)
19 {
20 for (int j = 0; j < s2.Length; j++)
21 {
22
23 int n;
24 //如果i,j都不小余1 那么n取该坐标左上方的值
25 if (i - 1 >= 0 && j - 1 >= 0)
26 {
27 //这里n的值等于所在坐标左上方的值
28 n = a[i - 1, j - 1];
29
30 }
31 //否则n=0
32 else
33 {
34 n = 0;
35 }
36 //如果s1[i]=s2[j]说明横列跟竖列的值相等,那么这个坐标内的值比它左上方的值大一位。
37 a[i, j] = s1[i] == s2[j] ? 1 + n : 0;
38 //如果该坐标内的值大于最大子字符串长度则最大子字符串长度变为现在坐标内的值。
39 //同时记录此时的横坐标。
40 if (a[i, j] > length)
41 {
42 length = a[i, j];
43 end = i;
44 }
45 }
46 }
47
48 ShowArrayStructure(s1, s2, a);
49 //从s1中取得数据,横坐标-最大长度加1
50 return s1.Substring(end - length + 1, length);
51 }
52
53 //输出构建的二维数组,用于观察数组结构
54 public static void ShowArrayStructure(string s1,string s2,int[,] LCSArray)
55 {
56 Console.WriteLine(" " + s2+"\n");
57 for (int i = 0; i < s1.Length; i++)
58 {
59 Console.Write(s1[i]+" ");
60 for (int j = 0; j < s2.Length; j++)
61 {
62 Console.Write(LCSArray[i, j]);
63 }
64 Console.WriteLine("\n");
65 }
66 }
总结:
两种方法写出来都OK。第二种方法更不容易想到,也更巧妙。但在面试过程中由于紧张的情绪不容易想到这样的方法,可以理解,如果能在面试中使用这种方法,必会给考官留下不错的印象~