📌  相关文章
📜  为给定数组获得严格的LIS所需的最小连接(1)

📅  最后修改于: 2023-12-03 14:48:58.175000             🧑  作者: Mango

为给定数组获得严格的LIS所需的最小链接

给定一个数组,我们希望找到构成最长的严格递增子序列 (LIS) 所需的最小连接数。

首先,我们需要了解什么是“严格递增”。在一个数组中,如果当前元素大于前一个元素,则我们称其为“递增”。如果没有相等的元素,则称其为“严格递增”。

那么,如何寻找最长的 LIS 呢?我们可以使用动态规划的方法。

动态规划

我们可以定义一个状态数组 $dp$,其中 $dp[i]$ 表示以第 $i$ 个元素结尾,最长的 LIS 的长度。此外,我们还需要另一个数组 $prev$,用于记录构成 $dp[i]$ 的 LIS 中,第 $i$ 个元素前面的那个元素在原数组中的下标。最终,我们需要以 $dp$ 数组中最大的值为结束点,倒推出整个 LIS。

具体的状态转移方程为:

$dp[i] = \max_{j=0}^{i-1}(dp[j]+1)$,其中 $j<i$ 且 $nums[i]>nums[j]$

这个方程的意思是,对于当前位置为 $i$ 的元素,我们可以将其添加到以 $j$ 结尾的 LIS 后面,成为一个新的 LIS,长度为 $dp[j]+1$。

同时,我们需要记录 $prev[i]$,即在构成 $dp[i]$ 的 LIS 中,第 $i$ 个元素前面的那个元素在原数组中的下标。也就是说,如果 $dp[i]=dp[j]+1$,那么 $prev[i]=j$。

最终,整个算法的流程如下:

  1. 初始化 $dp$ 和 $prev$ 数组。对于每个下标 $i$,我们先将 $dp[i]$ 的值赋为 $1$。因为每个元素本身就构成了一个 LIS,所以长度为 $1$。$prev[i]$ 的值赋为 $-1$,表示该元素前面没有元素。

  2. 对于每个下标 $i$,从 $0$ 到 $i-1$ 遍历 $dp$ 数组,更新状态转移方程。

  3. 找到 $dp$ 数组中的最大值 $max_len$,以及它所在的下标 $end$。

  4. 从 $end$ 开始倒推 LIS,直到 $prev[i]=-1$。将 LIS 中的元素反转即可。

  5. 返回 LIS 的长度以及 LIS 数组本身。

代码实现如下:

def find_lis(nums):
    n = len(nums)
    dp = [1] * n
    prev = [-1] * n
    for i in range(1, n):
        for j in range(i):
            if nums[i] > nums[j]:
                if dp[j] + 1 > dp[i]:
                    dp[i] = dp[j] + 1
                    prev[i] = j
    max_len = max(dp)
    end = dp.index(max_len)
    lis = []
    while end != -1:
        lis.append(nums[end])
        end = prev[end]
    lis.reverse()
    return max_len, lis
最小连接数

现在我们已经能够找到 LIS 了,但是如何求出最小连接数呢?

这里我们可以借助“最长公共子序列” (LCS) 的概念。LCS 是指在两个序列中,找到一个最长的公共子序列,可以不连续。例如在字符串 "abcde" 和 "ace" 中,最长公共子序列为 "ace"。

我们可以将原数组和 LIS 数组看作两段序列,然后找到它们的 LCS。连接数就等于原数组的长度减去 LCS 的长度和 LIS 的长度之和。

具体的算法实现可以使用动态规划。我们定义一个状态数组 $dp$,其中 $dp[i][j]$ 表示第一个序列中以第 $i$ 个元素结尾,第二个序列中以第 $j$ 个元素结尾的 LCS 长度。状态转移方程如下:

$$ dp[i][j] = \begin{cases} dp[i-1][j-1]+1, & a_i=b_j \ \max(dp[i-1][j], dp[i][j-1]), & a_i \neq b_j \end{cases} $$

其中 $a$ 表示原数组,$b$ 表示 LIS 数组。

最终,我们可以使用下面的代码实现求解最小连接数:

def min_connections(nums):
    _, lis = find_lis(nums)
    n, m = len(nums), len(lis)
    dp = [[0] * (m+1) for _ in range(n+1)]
    for i in range(1, n+1):
        for j in range(1, m+1):
            if nums[i-1] == lis[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    lcs = dp[n][m]
    return n - lcs - m
总结

这篇文章介绍了如何通过动态规划的方法,找到一个数组中最长的严格递增子序列以及构成它所需的最小连接数。具体来说,需要定义状态数组 $dp$ 和 $prev$,并使用状态转移方程更新 $dp$ 和 $prev$。然后,以 $dp$ 中的最大值为结束点,倒推 LIS。最后,计算原数组与 LIS 数组的 LCS 长度,从而求出最小连接数。

这个算法的时间复杂度是 $O(n^2)$,其中 $n$ 是数组的长度。如果需要进一步优化,可以使用二分查找和贪心算法,时间复杂度可以降为 $O(n\log n)$。