📅  最后修改于: 2023-12-03 15:39:02.158000             🧑  作者: Mango
在处理字符串序列时,常常需要寻找最长递增子序列的长度以解决一系列问题,如最长公共子序列,编辑距离等。
最长递增子序列(Longest Increasing Subsequence,简称LIS)是指在一个序列中,选取其中的一些数字可以组成一个新的递增的序列,这个新序列的长度尽可能长。例如,序列[10, 9, 2, 5, 3, 7, 101, 18]的最长递增子序列为[2, 3, 7, 101],其长度为4。
暴力解法即枚举所有可能的子序列,每次判断是否为递增子序列,记录最大长度。时间复杂度为O(2^n),显然效率较低。
动态规划是解决LIS问题的常用方法。该方法需要定义状态、状态转移方程、初始状态。具体实现如下:
定义状态:dp[i]表示以第i个元素为结尾的最长递增子序列的长度。
状态转移方程:要求dp[i],需要枚举前面所有小于nums[i]的元素j,将dp[j]加1,然后取所有可能的dp[j]+1中的最大值即可。
初始状态:dp[0]=1。
最终结果为dp数组中的最大值。
下面是具体的代码实现:
def lengthOfLIS(nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
dp = [1] * n
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
时间复杂度为O(n^2),空间复杂度为O(n)。
以上动态规划的解法中,对于每个元素i,需要枚举前面所有小于nums[i]的元素j,时间复杂度为O(n)。可以利用二分查找将时间复杂度优化到O(logn)。
具体实现如下:
定义状态:tails[i]表示长度为i+1的递增子序列最后一个元素的最小值。
状态转移方程:对于nums[i],如果其大于tails中的所有元素,将其插入tails末尾;否则,在tails中查找大于或等于nums[i]的第一个元素,并用nums[i]替换之。
初始状态:tails=[nums[0]]。
最终结果为tails数组的长度。
以下是代码实现:
def lengthOfLIS(nums: List[int]) -> int:
if not nums:
return 0
tails = [nums[0]]
for i in range(1, len(nums)):
if nums[i] > tails[-1]:
tails.append(nums[i])
else:
l, r = 0, len(tails) - 1
while l < r:
mid = l + (r - l) // 2
if tails[mid] >= nums[i]:
r = mid
else:
l = mid + 1
tails[l] = nums[i]
return len(tails)
时间复杂度为O(nlogn),空间复杂度为O(n)。
本文介绍了解决字符串中最长递增子序列的长度问题的两种方法,暴力解法和动态规划解法,以及利用二分查找优化的动态规划解法。二分查找优化可以将时间复杂度优化到O(nlogn)。在实际应用中应优先选择优化后的算法。