一、最长递增子序列(LIS)问题


最长递增子序列(LIS)问题:已知一个序列,找出最长单调递增子序列(不要求连续) 网上只找到计算长度的代码,没有找到输出最长序列的代码,因此只有部分参考价值,此处给出的代码,可以随机输出最长递增子序列中的一个


'''
最长递增子序列(LIS)问题:已知一个序列,找出最长单调递增子序列(不要求连续)
网上只找到计算长度的代码,没有输出最长序列的代码,因此只有部分参考价值
'''
import random
import numpy as np

# 方法一:最长公共子序列法
# a = [7,3,8,6,19,13,7,10]
# 排序并去重
# n = len(a)
# b = np.zeros(n)
# for k in range(n):
# b[k] = a[k]
# b.sort()
# 查找序列 a 和 b 的最长公共子序列


# 方法二:动态规划法
def LIS(a):
n = len(a)
arr_dict = []
dict = {0: -1}
arr_dict.append({0: a[0]})
for i in range(1, n):
flag = False
for j in range(len(arr_dict) - 1, -1, -1):
temp_dict = arr_dict[j]
for key, value in temp_dict.items():
if value < a[i]:
dict[i] = key
if (j == (len(arr_dict) - 1)):
arr_dict.append({i: a[i]})
flag = True
break
else:
flag = True
arr_dict[j+1][i] = a[i]
break
if flag:
break
if (not flag):
arr_dict[0][i] = a[i]
dict[i] = -1

# arr_arr 的长度就是最长递增子序列的长度
length = len(arr_dict)
print(arr_dict)
print(dict)

# 随便取一个最长递增子序列
sub = np.zeros(length)
j = length - 1
last = random.choice(list(arr_dict[j]))
sub[j] = a[last]
while j > 0:
j = j - 1
last = dict[last]
sub[j] = a[last]

return length, sub

# 输入部分
a = [7,26,20,17,8,3,11,22] # 预期输出:7,8,11,12
# a = [7,3,8,6,19,13,7,10] # 预期输出:3,6,7,10
# a = [8,2,1,3,2,4,5,1,2] #预期输出:2,3,4,5
# a = [2,8,3,4,5,1,2] #预期输出:2,3,4,5

length, sub = LIS(a)
print("最长递增子序列的长度为", length)
print("最长递增子序列是", sub)

二、最长公共子序列(LCS)问题


最长公共子序列(LCS):给定两个序列 X 和 Y,找出最长的公共子序列(不要求连续) 思路参考了: 1、穷举法:pass 2、动态规划法,此代码可以随机输出最长递增子序列中的一个;如果想要输出所有的最长递增子序列,在第三部分(LICS)中有提供这个方法,往后看就行


'''
最长公共子序列(LCS):给定两个序列 X 和 Y,找出最长的公共子序列(不要求连续)
思路参考了:javascript:void(0)
1、穷举法:pass
2、动态规划法:先输出一个中间数组,res[i][j]表示当str1长度取前i,str2长度取前j时,它们的最长公共子序列的元素个数;然后通过倒推法,往前推荐,找到一个最长公共子序列
'''
import numpy as np

def LCS(string1,string2):
len1 = len(string1)
len2 = len(string2)
res = [[0 for i in range(len1+1)] for j in range(len2+1)]
for i in range(1,len2+1):
for j in range(1,len1+1):
if string2[i-1] == string1[j-1]:
res[i][j] = res[i-1][j-1]+1
else:
res[i][j] = max(res[i-1][j],res[i][j-1])

# 求出最长公共子序列
length = res[-1][-1]
sub = ['' for i in range(length)]
i = len1 # 8
j = len2 # 9
while((i != 0) & (j != 0)):
if string1[i-1] == string2[j-1]:
sub[length - 1] = string1[i - 1]
length = length - 1
i = i - 1
j = j - 1
else:
try:
if res[j - 1][i] >= res[j][i - 1]:
j = j - 1
else:
i = i - 1
except:
a = 1

for i in range(1,len2+1):
for j in range(1,len1+1):
print(res[i][j], " ", end='')
print()

return sub,res[-1][-1]

# sub, length = LCS("phenomenon","enughton")
# 预期输出:['e', 'n', 'o', 'n'], 长度为 4
# sub, length = LCS("helloworld","loop")
# 预期输出:['l', 'o', 'o'], 长度为 3
sub, length = LCS("13456778","357486782")
# 预期输出:[3,5,7,7,8], 长度为 5 [3,4,6,7,8] 也对
# res:
# 0 1 1 1 1 1 1 1
# 0 1 1 2 2 2 2 2
# 0 1 1 2 2 3 3 3
# 0 1 2 2 2 3 3 3
# 0 1 2 2 2 3 3 4
# 0 1 2 2 3 3 3 4
# 0 1 2 2 3 4 4 4
# 0 1 2 2 3 4 4 5
# 0 1 2 2 3 4 4 5
print(sub, length)

三、最长公共递增子序列(LICS)


最长公共递增子序列(LICS):结合最长公共子序列问题(LCS)、最长递增子序列问题(LIS) 思路:找出所有的最长公共子序列,分别取它们的最长递增子序列,取最长的作为结果 难点:找出所有的最长公共子序列,参考了


'''
最长公共递增子序列(LICS):结合最长公共子序列问题(LCS)、最长递增子序列问题(LIS)
思路:找出所有的最长公共子序列,分别取它们的最长递增子序列,取最长的作为结果
找出所有的最长公共子序列,参考了:javascript:void(0)
'''
import random
import numpy as np

# 构建初始数组,求所有的最长公共子序列
def all_LCS(str1,str2):
len1 = len(str1)
len2 = len(str2)

# len2 是行数, len1 是列数
res = [[[0, ''] for i in range(len1 + 1)] for j in range(len2 + 1)]

# i是行,j是列
for i in range(1, len2 + 1):
for j in range(1, len1 + 1):
if (str2[i-1] == str1[j-1]):
res[i][j][0] = res[i-1][j-1][0] + 1
res[i][j][1] = '↖'
else:
if res[i-1][j][0] > res[i][j-1][0]:
res[i][j][0] = res[i-1][j][0]
res[i][j][1] = '↑'
elif res[i-1][j][0] < res[i][j-1][0]:
res[i][j][0] = res[i][j-1][0]
res[i][j][1] = '←'
else:
res[i][j][0] = res[i][j - 1][0]
res[i][j][1] = '┘'

# 将得到的数组输出
# for i in range(1, len2 + 1):
# for j in range(1, len1 + 1):
# if j != len1:
# print(res[i][j], end=" ")
# else:
# print(res[i][j])

sub_set = set()
backTrace(res, str1, len2, len1, '', res[-1][-1][0], sub_set)
return sub_set

# 递归调用,求所有的最长公共子序列
def backTrace(res, str1, i, j, sub, longest_len, sub_set):
if ((i == 0) & (j == 0)):
sub = sub[::-1]
if len(sub) == longest_len:
sub_set.add(sub)
return

if res[i][j][1] == '↖':
sub = sub + str1[j - 1]
backTrace(res, str1, i-1, j-1, sub, longest_len, sub_set)
elif res[i][j][1] == '←':
backTrace(res, str1, i, j-1, sub, longest_len, sub_set)
elif res[i][j][1] == '↑':
backTrace(res, str1, i-1, j, sub, longest_len, sub_set)
elif res[i][j][1] == '┘':
backTrace(res, str1, i, j - 1, sub, longest_len, sub_set)
backTrace(res, str1, i-1, j, sub, longest_len, sub_set)
else:
backTrace(res, str1, 0, 0, sub, longest_len, sub_set)

# 求最长递增子序列
def LIS(a):
n = len(a)
arr_dict = []
dict = {0: -1}
arr_dict.append({0: a[0]})
for i in range(1, n):
flag = False
for j in range(len(arr_dict) - 1, -1, -1):
temp_dict = arr_dict[j]
for key, value in temp_dict.items():
if value < a[i]:
dict[i] = key
if (j == (len(arr_dict) - 1)):
arr_dict.append({i: a[i]})
flag = True
break
else:
flag = True
arr_dict[j+1][i] = a[i]
break
if flag:
break
if (not flag):
arr_dict[0][i] = a[i]
dict[i] = -1

# arr_arr 的长度就是最长递增子序列的长度
length = len(arr_dict)

# 随便取一个最长递增子序列
sub = ['' for i in range(length)]
j = length - 1
last = random.choice(list(arr_dict[j]))
sub[j] = a[last]
while j > 0:
j = j - 1
last = dict[last]
sub[j] = a[last]

sub_str = "".join(sub)

return length, sub_str

# 输入
sub_set = all_LCS("13456778","357486782") # 预期输出:最长公共递增子序列有但不限于 {'35678', '34678'} , 长度为 5
# sub_set = all_LCS("821324512","2834512") # 预期输出:最长公共递增子序列有但不限于 {'2345'} , 长度为 4
# sub_set = all_LCS("XBACBDEAB","BXCDEAB") # 预期输出:最长公共递增子序列有但不限于 {'BCDE'} , 长度为 4
# sub_set = all_LCS("abcicba","abdkscab") # 预期输出:最长公共递增子序列有但不限于 {'abc'} , 长度为 3
# sub_set = all_LCS("ABCBDAB","BDCABA") # 预期输出:最长公共递增子序列有但不限于 {'BC', 'BD'} , 长度为 2

print("所有的最长公共子序列是", sub_set)
max_len = 0
max_sub_set = set()
for str in sub_set:
length, sub_str = LIS(str)
if length > max_len:
max_len = length
max_sub_set.clear()
max_sub_set.add(sub_str)
elif length == max_len:
max_sub_set.add(sub_str)

print("最长公共递增子序列有但不限于", max_sub_set, ", 长度为", max_len)

以上代码全部都是自测,没有在专门的算法网站中测试过,所以如果有什么问题,请大家积极留言,这样才能不断完善,谢谢大家!