📜  最大化不属于最长递增子序列的所有元素的总和(1)

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

最大化不属于最长递增子序列的所有元素的总和

在算法竞赛和实际开发中,我们常常需要对一些序列进行一些操作和分析。其中,最长递增子序列被广泛地应用于许多场合。然而,在一些特殊的场合下,我们并不需要求出最长递增子序列本身,而只需要求出不属于最长递增子序列的所有元素的总和。

什么是最长递增子序列

首先,我们需要了解一下什么是最长递增子序列(Longest Increasing Subsequence,简称 LIS)。

给定一个序列 $a_1,a_2,...,a_n$,我们称 $a_{i_1},a_{i_2},...,a_{i_k}$ 为 $a_1,a_2,...,a_n$ 的一个子序列,如果 $1\le i_1< i_2<...< i_k \le n$。该子序列是递增的,当且仅当 $a_{i_1}< a_{i_2}<...< a_{i_k}$。如果存在多个递增子序列的长度相同,那么我们称长度最大的递增子序列为最长递增子序列。

最大化不属于 LIS 的元素总和的问题

我们考虑一个问题:如何找到一个数列的最长递增子序列之外的所有数字的和最大。比如,通过考察下图,我们发现最长递增子序列为 1, 2, 5, 6,因此不属于最长递增子序列的元素是 4 和 3,其总和为 7。

          1  2  5  6  3  4

对于这个问题,我们可以先求出最长递增子序列,然后将不在最长递增子序列中的元素求和,但是由于使用 LIS 算法的时间复杂度为 $O(n^2)$ 或者 $O(n\log n)$,并不适合求解这种问题。

事实上,我们可以不需要使用 LIS 的思想,也能够解决这个问题。我们考虑一个贪心的思路,不断地将未加入最长递增子序列(以下简称“未加入集合”)的最小元素加入集合中。因为加入未加入集合中的数字,最小数字会更小,从而使得后面的数字可能被加入到最长递增子序列中。因此,通过贪心策略,我们也能够求解最大化不属于 LIS 的所有元素的总和问题。

算法描述

通过观察上面的贪心思路,我们可以给出下面的算法描述:

  1. 定义变量 $ans$ 表示不属于 LIS 的元素的总和,$minn$ 表示 LIS 中最后一个数字,初始时,令 $minn$ 赋值为无穷大。
  2. 从前往后遍历序列中的每一个数字 $a_i$,进行如下操作:
    1. 如果 $a_i$ 大于 $minn$,则将 $a_i$ 加入到 LIS 中,令 $minn$ 等于 $a_i$。
    2. 如果 $a_i$ 小于等于 $minn$,则将 $a_i$ 加入到不属于 LIS 的元素中,更新 $ans$ 的值。
  3. 返回 $ans$ 的值。
算法实现

我们可以将上述想法翻译成 Python 代码。下面是对应的代码片段:

def solve(numbers):
    ans = 0
    minn = float('inf')
    for i in numbers:
        if i > minn:
            minn = i
        else:
            ans += i
    return ans

这个算法的时间复杂度为 $O(n)$,因此非常适合求解这个问题。

测试样例
assert solve([1, 2, 5, 6, 3, 4]) == 7
assert solve([2, 1, 3, 5, 4]) == 4
assert solve([5, 4, 3, 2, 1]) == 10

上面的样例中,第一个样例已经在前面提到过了,第二个样例中,最长递增子序列为 1, 3, 5,因此不属于最长递增子序列的元素是 2 和 4,其总和为 4。第三个样例中不存在递增子序列,因此所有的数字都不属于递增子序列,其总和为 10。