📅  最后修改于: 2023-12-03 14:55:19.410000             🧑  作者: Mango
本题目旨在探讨如何求解一个序列中最大总和子序列,且子序列中任意两个元素之差至少为2。
给定一个长度为n(n<=10^6)的整数序列 a ,求其最大总和子序列 b ,且 b 中任意两个元素之差至少为2。
最朴素的方法是枚举所有可能的子序列,然后找出其中最大总和的那个子序列,但是这个算法的时间复杂度是O(n^3),不太适用于大规模数据。
动态规划算法是求解最大总和子序列的经典方法。我们设 sum[i] 为以第i个元素结尾的最大总和子序列的值,那么有如下的状态转移方程:
sum[i] = max{sum[j] + a[i]} (j<i,a[j]+2<=a[i])
其中max{}表示在大括号内的表达式取最大值,这个式子的意思是,以第i个元素结尾的最大总和子序列是从前面某个位置j开始,一直包括第i个元素的一个连续子序列,并且要求这个子序列中任意两个元素之差至少为2。
时间复杂度为O(n^2),可通过样例。
我们可以把序列分成多个连续的子序列,每个子序列中元素值之间的差都至少为2。然后对每个子序列求最大总和,再把每个子序列的最大总和加起来就得到了整个序列的最大总和。设 maxsum[i] 表示以第 i 个位置结尾的子序列的最大总和,求解方式是:对于每个位置 i ,我们分两种情况:
这个算法的时间复杂度为O(n),比动态规划更快。但是贪心算法存在一些特殊情况下会得到错误的结果,比如序列 [5,4,3,2,1,2,3,4,5] ,贪心算法的结果是 30 ,但是实际上最大总和子序列是 [1,2,3,4,5],其总和为 15。因此需要对贪心算法进行一些优化。
在贪心算法中,我们通过把序列分成多个连续的子序列来确保子序列中元素值之间的差都至少为2。如果一个元素不能加入到当前正在填充的子序列中,则新开一个子序列来填充,直到所有元素都被放入子序列中。但是上述的分子序列的过程并不好操作,我们可以采用其他的方式来达到同样的目的,如下:
首先将整个序列排序,并将所有负数交换到最前面,这样序列就被分成了三个部分:负数、零、正数。我们每次选择一个不在最大总和子序列中的最小正整数 x ,然后把所有值在区间[x,x+1)范围内的正整数全部加入到最大总和子序列中。显然,对于新加入的数 y ,如果存在 z∈[x,y) ,它不在最大总和子序列中,那么我们可以把 z 从最大总和子序列中删除,并加入 y。这样可以保证任意两个元素之差至少为2,且时间复杂度为O(nlogn)。
def max_subsequence_sum(a: list) -> int:
n = len(a)
max_sum = a[0]
dp = [0] * n
dp[0] = a[0]
for i in range(1, n):
dp[i] = a[i]
for j in range(i-1, -1, -1):
if a[j] + 2 <= a[i]:
dp[i] = max(dp[i], dp[j] + a[i])
max_sum = max(max_sum, dp[i])
return max_sum
def max_subsequence_sum(a: list) -> int:
a.sort()
a = a[a.index((a + [0])[0][0]):]
n = len(a)
x = 1
sum_ = 0
while x <= n:
while x < n and a[x] == a[x-1]+1:
x += 1
s = sum(a[i] for i in range(x) if a[i] > 0)
if s > 0:
sum_ += s
if x < n:
x += 1
return sum_