📅  最后修改于: 2023-12-03 14:55:23.429000             🧑  作者: Mango
最长递增子序列(Longest Increasing Subsequence,简称 LIS)是指在一个给定的序列中,找到一个最长的子序列使得其中的元素是按升序排列的。
例如,给定序列 {10, 22, 9, 33, 21, 50, 41, 60},则其 LIS 的长度为 5,具体为 {10, 22, 33, 50, 60}。
这是最简单直观的解法,即对于每个元素,枚举它之前的所有元素,找到其中最长的递增子序列,并在其中加入当前元素。时间复杂度为 $O(2^n)$,不适用于数据量大的情况。
动态规划是一种求解最优问题的有效方法。对于 LIS,可以使用动态规划求解。设 $L_i$ 表示以第 $i$ 个元素为结尾的最长递增子序列长度,则有如下状态转移方程:
$$ L_i=\max(L_j+1) \quad \text{if } j < i \text{ and } arr_j < arr_i $$
即以第 $i$ 个元素为结尾的最长递增子序列长度等于前面小于第 $i$ 个元素的所有元素的最长递增子序列长度加一。
时间复杂度为 $O(n^2)$。
def LIS(arr):
n = len(arr)
if n == 0:
return 0
dp = [1] * n
for i in range(1, n):
for j in range(i):
if arr[j] < arr[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
在解法二的基础上,考虑优化时间复杂度。注意到状态转移方程中,我们只需要在前面小于当前元素的所有元素中找到 LIS 长度最大的一个就可以了,因此可以使用二分查找来优化。
具体来说,我们维护一个递增的数组 $d$,其中每个元素代表以当前长度结尾的 LIS 中末尾元素的最小值。对于每个元素,我们使用二分查找找到 $d$ 数组中第一个大于等于它的位置,并将该位置上的元素替换为当前元素。
时间复杂度为 $O(n \log n)$。
def LIS(arr):
n = len(arr)
if n == 0:
return 0
d = [arr[0]]
for i in range(1, n):
if arr[i] > d[-1]:
d.append(arr[i])
else:
l, r = 0, len(d) - 1
while l < r:
mid = (l + r) // 2
if d[mid] >= arr[i]:
r = mid
else:
l = mid + 1
d[l] = arr[i]
return len(d)
LIS 是一个经典的算法问题,其动态规划解法和贪心 + 二分查找解法都有其标志性的思想。在实际应用中,根据数据量和时间复杂度的需求,可以选择相应的解法。