LCS、LIS、LCString:算法面试中常见解决思路和代码
LCS、LIS、LCString:算法面试中常见解决思路和代码
1. LCS:largest common subsequence最长公共子序列
假定两个字符串a、b长度分别为m和n,且有m<=n.
1.1 暴力求解BF
将两个字符串中较短的一个进行全排列,一共的2^m个可能的序列,然后分别与另一个字符串进行比对,找出最长的子序列。
1.2 动态规划
如果ai == bj,那么cij等于c[i-1][j-1]+1;
如果ai != bj,那么有两种情况要么是ai-1与bj的组合的结果;要么是ai与bj-1的自核结果。如下图:
即可求出cij这个二维数组,里面的每一个元素代表了a、b字符串矩阵中的最大子序列的长度:
1 def lcs(s1, s2): 2 res = [([0] * (len(s2) + 1)) for i in range(len(s1) + 1)] 3 flag = [([0] * (len(s2) + 1)) for i in range(len(s1) + 1)] 4 for i in xrange(len(s1) + 1): 5 for j in xrange(len(s2) + 1): 6 if i == 0 or j == 0: 7 res[i][j] = 0 8 9 elif s1[i - 1] == s2[j - 1]: 10 res[i][j] = res[i - 1][j - 1] + 1 11 flag[i][j] = 'o' 12 else: 13 if res[i - 1][j] < res[i][j - 1]: 14 res[i][j] = res[i - 1][j] 15 flag[i][j] = 'l' 16 else: 17 res[i][j] = res[i][j - 1] 18 flag[i][j] = 'u' 19 20 #return res[len(s1)][len(s2)] 21 return res, flag
该代码我是考虑的之后子序列回溯的问题——flag,无伤大雅。
如果仅仅是想要得到最长的子序列长度的话,只需要得到res[len(s1)][len(s2)]即可。
该两图摘自网络博客,具体链接见底部。
回溯找到LCS代码如下:
1 def lcs2(s1, s2): 2 flag = lcs(s1, s2) 3 res = '' 4 i = len(flag)-1 5 j = len(flag[0])-1 6 while i != 0 and j != 0: 7 print i,j 8 if flag[i][j] == 'o': 9 res += s1[i-1] 10 i -= 1 11 j -= 1 12 elif flag[i][j] == 'l': 13 j -= 1 14 else: 15 i -= 1 16 return ''.join(reversed(res))
2. LCString最长公共子串问题
与LCS的区别在于是否连续
其实已经有了LCS的思想,再求公共子串的更加容易,只需将动态规划的思想改变一下,当遇到ai != bj的情况令cij=0即可。
细节不再赘述。
1 def lcstring(s1, s2): 2 res = [([0] * (len(s2) + 1)) for i in range(len(s1) + 1)] 3 maxL = 0 4 for i in xrange(len(s1) + 1): 5 for j in xrange(len(s2) + 1): 6 if i == 0 or j == 0: 7 res[i][j] = 0 8 9 elif s1[i - 1] == s2[j - 1]: 10 res[i][j] = res[i - 1][j - 1] + 1 11 maxL = max(res[i][j], maxL) 12 else: 13 res[i][j] = 0 14 return maxL
回溯代码没有必要,有很多种方式
3. LIS:largest increament subsequence 最长递增子序列
最长递增子序列并不是两个数列(其实字符串和数列都无所谓)
3.1 捷径算法
首先说一下一种捷径,如果已经实现了LCS,那么只需要一步即可得到LIS:
将该数列进行排序,然后与原数列进行LCS即可得到LIS!!!!
原数列:4,7,2,5,8,9 =a
排序后:2,5,4,7,8,9 =b
对其调用lcs(a, b)即可。
3.2 暴力求解BF
比如数列 4,7,2,5,8,9
第一个数为4,直接明了:res[1]
第二个数为7,有比他小的所以:res[2] = res[1] + 1
第三个数为2,前面没有比他小的res[3] = 1
第四个数为5,只有一个比他小的res[4] = res[3] + 1
第五个数为8,前面有好几个比他小的,所以找到max(res...) + 1
.......
一句话:就是找到在i之前的比i小的最大长度的序列的长度,很明显是n^2的复杂度。
1 def lis(l): 2 res = [0] * len(l) 3 maxl = 0 4 for i in xrange(len(l)): 5 res[i] = 1 6 for j in xrange(i): 7 if l[i] >= l[j] and (res[j]+1) >= res[i]: 8 res[i] = res[j] + 1 9 if maxl < res[i]: 10 maxl = res[i] 11 return maxl
回溯的话直接遍历res找到每一个元素即可。
3.3 贪心+二分法
自行维护一个数组res[] 用来记录最小序列的可能性,但是并不是真正的最长递增序列的真是值。目的仅仅是将其LIS的长度计算出来!!!!如果想要得到真正的LIS序列,需要记录其他的东西。
举个栗子:4,7,2,5,8,9
step1:res[4]
step2:res[4,7]
step3:res[2,7]
step4:res[5,7]
step5:res[5,7,8]
step:res[5,7,8,9]
递增的长肯定是res的长度,但是真是的LIS并不是5789而是2589或者4589或者4789并不是唯一的。
二分的作用就是在更换res的时候的时间复杂度从n^2变成nlogn
1 def binary_search(l, a): 2 end = len(l) 3 start = 1 4 while end > start: 5 mid = (end + 1) >> 1 6 if l[mid] <= a: 7 start = mid + 1 8 else: 9 end = mid - 1 10 return start 11 12 def lis2(l): 13 res = [] 14 for i in xrange(len(l)): 15 if len(res) == 0 or res[-1] < l[i]: 16 res.append(l[i]) 17 elif res[-1] > l[i]: 18 index = binary_search(res, l[i]) 19 res[index-1] = l[i] 20 return len(res)
3.4 树状数组维护
https://blog.csdn.net/George__Yu/article/details/75896330
参考链接:
https://blog.csdn.net/qq_31881469/article/details/77892324
https://blog.csdn.net/lxt_Lucia/article/details/81206439
http://www.cnblogs.com/GodA/p/5180560.html
有任何疑问请留言或者发送至邮箱397585361@qq.com