【问题标题】:Finding the longest zero-sum subsequence寻找最长的零和子序列
【发布时间】:2015-12-10 07:45:00
【问题描述】:

警告:这不是“寻找总和为零的最长子数组”问题的一个实例

我想知道是否有任何算法可以找到序列中总和为零的最大子序列的长度(即元素可以是连续的或不连续的),例如

S = {1, 4, 6, -1, 2, 8, -2}
     ^         ^  ^      ^
maximum length = 4

我搜索了它,但我找不到任何东西

【问题讨论】:

  • 这会更好地表达为“寻找最大的零和子集”,因为您根本不关心元素的顺序。

标签: algorithm language-agnostic


【解决方案1】:

这是subset sum 问题的一个细微变化。

d[i] = maximum length of a subsequence that sums to i。最初,这都是零。如果你的数字都是正数,你可以这样做:

s = 0
for i = 1 to n:
    s += a[i]
    for j = s down to a[i]:
        d[j] = max(d[j],               <- keep j as it is
                   d[j - a[i]] + 1     <- add a[i] to j - a[i], obtaining sum j
                  )

return ???

但是,这并没有考虑到存在负面元素的可能性。为了处理这些,您可以使用两个字典而不是数组:

a = [1, 4, 6, -1, 2, 8, -2] # the input array
d1 = {0: 0} # first dictionary: explicitly initialize d[0] = 0
d2 = {0: 0} # second dictionary is the same initially
n = len(a) # the length of the input array

for i in range(n): # for each index of the input array
    for j in d1: # for each value in the first dictionary

        x = 0
        if j + a[i] in d2: # if we already have answer for j + a[i] 
                           # in the second dictionary, we store it
            x = d2[j + a[i]]

        d2[j + a[i]] = max(x, d1[j] + 1) # add a[i] to the j in the first dictionary 
                                         # and get a new value in the second one,
                                         # or keep the existing one in the second dictionary,
                                         # if it leads to a longer subsequence


    d1 = dict(d2) # copy the second dictionary into the first.
                  # We need two dictionaries to make sure that 
                  # we don't use the same element twice

print(d1[0]) # prints 4

如果你添加一些常量,你也可以用数组来实现这一点,这样你就不会访问负索引,但字典更干净。

时间复杂度为O(n*S),其中S 是数组中所有数字的总和,n 是数组中元素的数量。

【讨论】:

  • 也许它没有多大帮助,但我想到了一个摆脱单个数组/字典的技巧:你可以做 2 遍——用down to 循环处理所有正数您建议在顶部,然后是带有to 循环的所有负数(反之亦然)。
  • @IVlad 我不确定我是否理解第一个算法.. 假设我有一个列表3 2,我最终应该得到d[3] = 1, d[2] = 1d[5] = 2.. 如果我使用你的第一个段落算法我也得到错误的值,如d[4] = 1..d[4] 怎么可能是 1?没有办法有一个总和为 4 的子序列!
  • @Dean 你确定你像我写的那样实现了内部循环吗? “下降”部分非常重要。
【解决方案2】:

还有一个使用动态编程和函数式编程的解决方案。在javascript中:

function maxSumZeroAcc(arr, sumS, nbElt) {
//returns nbElt + the size of the longest sequence summing to sumS
  if(arr.length === 0) {
    return (sumS===0)?nbElt:0;
  }
  else {
    return Math.max(maxSumZeroAcc(arr.slice(1), sumS, nbElt),maxSumZeroAcc(arr.slice(1), sumS-arr[0], nbElt+1));
  }
}

function maxSumZero(arr) {
//simply calls the previous function with proper initial parameters
  return maxSumZeroAcc(arr, 0, 0);
}

var myS = [1,4,6,-1,2,8,-2];
console.log(maxSumZero(myS));//returns 4

代码的核心是函数maxSumZeroAcc(arr, sumS, nbElt),它返回arr中最长序列的大小相加后的sumS——sumSnbElt是两个辅助参数集在函数maxSumZero 中。

maxSumZeroAcc 背后的想法是,我们正在寻找的最大值是应用于数组尾部的 maxSumZeroAcc 的最大值(我们只是丢弃第一个元素)或应用于数组尾部的 maxSumZeroAcc(.,sumS-arr[0],nbElt+1) 的最大值数组(我们考虑第一个元素,而不是找到元素之和 sumS,我们寻找元素之和 sumS 减去第一个元素)。

这个解决方案写起来很短,也很容易理解,但复杂性很差,在O(2^n) 中,其中n 是数组的大小。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-08
    • 2020-04-14
    • 2011-06-03
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多