【问题标题】:Algorithm to find the maximum subsequence of an array of positive numbers . Catch : No adjacent elements allowed查找正数数组的最大子序列的算法。 Catch :不允许相邻元素
【发布时间】:2009-02-25 00:11:30
【问题描述】:

例如,给定

A = [1,51,3,1,100,199,3], maxSum = 51 + 1 + 199 = 251.

显然max(oddIndexSum,evenIndexSum) 确实有效。

我遇到的主要问题是我无法为元素提出选择标准。 给定选择标准,拒绝标准是微不足道的。

标准的最大子序列算法似乎不适用于这里。 我尝试过动态编程方法,但也想不出。 我能想到的唯一方法是使用遗传算法。

你会如何处理这个问题?

【问题讨论】:

  • 你的问题中隐含的 K = 3 是常量还是变量...
  • @ShuggyCoUk :您指的是子序列的长度吗?那是一个变量。
  • 贪心算法也不是在所有情况下都有效。 叹息
  • 这是我几周前参加的期中考试的一个问题。我使用了一个贪心算法。我听说动态编程方法也有效。

标签: arrays algorithm


【解决方案1】:

如果你保持两种状态,你可以逐步构建最大子序列:

def maxsubseq(seq):
  # maximal sequence including the previous item
  incl = []
  # maximal sequence not including the previous item
  excl = []

  for i in seq:
    # current max excluding i
    if sum(incl) > sum(excl):
      excl_new = incl
    else:
      excl_new = excl

    # current max including i
    incl = excl + [i]

    excl = excl_new

  if sum(incl) > sum(excl):
    return incl
  else:
    return excl


print maxsubseq([1,4,6,3,5,7,32,2,34,34,5])

如果您还想在列表中包含否定元素,则必须添加一些 if。

相同 -- 在较少的行中

def maxsubseq2(iterable):
    incl = [] # maximal sequence including the previous item
    excl = [] # maximal sequence not including the previous item

    for x in iterable:
        # current max excluding x
        excl_new = incl if sum(incl) > sum(excl) else excl
        # current max including x
        incl = excl + [x]
        excl = excl_new

    return incl if sum(incl) > sum(excl) else excl

同样——消除sum()

def maxsubseq3(iterable):
    incl = [] # maximal sequence including the previous item
    excl = [] # maximal sequence not including the previous item
    incl_sum, excl_sum = 0, 0
    for x in iterable:
        # current max excluding x
        if incl_sum > excl_sum:
            # swap incl, excl
            incl, excl = excl, incl
            incl_sum, excl_sum = excl_sum, incl_sum
        else:
            # copy excl to incl
            incl_sum = excl_sum #NOTE: assume `x` is immutable
            incl     = excl[:]  #NOTE: O(N) operation
        assert incl is not excl
        # current max including x
        incl.append(x)
        incl_sum += x
    return incl if incl_sum > excl_sum else excl

好吧,我们来优化一下……

总运行时间 O(n) 的版本:

def maxsubseq4(iterable):
    incl = [] # maximal sequence including the previous item
    excl = [] # maximal sequence not including the previous item
    prefix = [] # common prefix of both sequences
    incl_sum, excl_sum = 0, 0
    for x in iterable:
        if incl_sum >= excl_sum:
            # excl <-> incl
            excl, incl = incl, excl
            excl_sum, incl_sum = incl_sum, excl_sum
        else:
            # excl is the best start for both variants
            prefix.extend(excl) # O(n) in total over all iterations
            excl = []
            incl = []
            incl_sum = excl_sum
        incl.append(x)
        incl_sum += x
    best = incl if incl_sum > excl_sum else excl
    return prefix + best # O(n) once

【讨论】:

  • 这个算法是 O(N2)。循环seq O(N) 次 (excl + [x]),即 O(N) -> O(N)*O(N) -> O(N2)。
  • maxsubseq4() 看起来像 O(N)。
  • 为什么不prefix.extend(incl if incl_sum &gt; excl_sum else excl)?它所需的时间和内存至少是(prefix + best) 变体的两倍。
  • 只需(最好的前缀)就可以了。 O(1),因为你在数。
【解决方案2】:

Chris 的答案在列表 [9,10,9] 上失败,产生 10 而不是 9+9 = 18。

乔不太对劲。旅行推销员要求您访问每个城市,而这里没有类似的。

一种可能的解决方案是递归解决方案:

function Max_route(A)
    if A's length = 1 
        A[0]
      else
        maximum of
          A[0]+Max_route(A[2...])
          Max_route[1...]

这与简单的斐波那契函数具有相同的大 O,如果您除了简单地获得正确答案之外还关心效率,那么它应该会产生一些相同的优化(例如记忆化)。

-- MarkusQ

[编辑] ---

因为有些人似乎没有明白这一点,所以我想解释一下我所说的记忆是什么意思以及它为什么重要。

您可以包装上面的函数,以便它只计算每个数组的值一次(第一次调用它),并且在后续调用中将简单地返回保存的结果。这将占用 O(n) 空间,但会在恒定时间内返回。这意味着整个算法将在 O(n) 时间内返回,比上面不太混乱的版本的指数时间要好。我假设这很好理解。

[二次编辑]------------------------------

如果我们稍微扩展一下上面的内容并将其分开,我们会得到:

f []      :- [],0
f [x]     :- [x],x
f [a,b]   :- if a > b then [a],a else [b],b
f [a,b,t] :- 
    ft = f t
    fbt = f [b|t]
    if a + ft.sum > fbt.sum
        [a|ft.path],a+ft.sum
      else
        fbt

我们可以仅使用大小为 n 的整数和布尔数组以及以下操作展开为伪基础:1) 数组索引和索引数组赋值,2) 整数数学,包括比较,3) if/then/else,和 4) 一个 O(n) 的单循环:

dim max_sum_for_initial[n],next_to_get_max_of_initial[n],use_last_of_initial[n]

max_sum_for_initial[0] = 0
next_to_get_max_of_initial[0] = -1
use_last_of_initial[0] = false

max_sum_for_initial[1] = a[0]
next_to_get_max_of_initial[1] = -1
use_last_of_initial[1] = true

if a[0] > a[1]
    max_sum_for_initial[2] = a[0]
    next_to_get_max_of_initial[2] = 0
    use_last_of_initial[2] = false
  else
    max_sum_for_initial[2] = a[1]
    next_to_get_max_of_initial[1] = -1
    use_last_of_initial[2] = true

for i from 3 to n
    if a[i]+max_sum_for_initial[i-2] > max_sum_for_initial[i-1]
        max_sum_for_initial[i] = a[i]+max_sum_for_initial[i-2]
        next_to_get_max_of_initial[i] = i-2
        use_last_of_initial[i] = true
      else
        max_sum_for_initial[i] = max+sum_for_initial[i-1]
        next_to_get_max_of_initial[i] = i-1
        use_last_of_initial[i] = false

最后我们可以提取结果(以相反的顺序):

for i = n; i >= 0; i = next_to_get_max_of_initial[i]
    if use_last_of_initial[i] then print a[i]

请注意,我们刚刚手动执行的操作是现代语言的优秀编译器应该能够通过尾递归、记忆等来完成的。

我希望这已经足够清楚了。

-- MarkusQ

是 O(n)。

【讨论】:

  • 你在cmets中提到的线性算法是什么?迄今为止最快的算法是@sth 的算法,时间上为 O(N**2),空间上为 O(N)。 stackoverflow.com/questions/584228/…
  • @sth's 在时间和空间上是线性的,就像这个具有智能记忆功能的一样。
  • 你的算法不是线性的。一个朴素的递归斐波那契函数和一个更有效的迭代函数使用不同的算法。您能否提供一个包含 O(N) 算法的答案的链接(而不仅仅是引用它可以在 O(N) 中完成)。
  • J.F. Sebastian——上面的算法与记忆是线性的(需要 O(n) 空间)。我没有直接写出来,因为a)许多现代语言都包含了它的功能,b)它会使代码变得混乱和模糊。
  • 我试图为你的算法想出智能记忆(这将是 O(N) 的时间)但失败了。我显然不够聪明:) 看看@mattiast 的回答。他的记忆技术(动态编程)仅适用于最大子序列的总和。它不适用于 子序列
【解决方案3】:
find_max(int t, int n)
{

     if(t>=n)
       return 0;
     int sum =0, max_sum =0;
     for(int i=t; i<n; ++i)
     {
       sum = sum + A[i];
       for(int j=i+2; j<n; ++j)
          sum = sum + find_max(A[j], n);
       if(sum > max_sum)
          max_sum = sum;
     }
     return max_sum;

}

以上是递归解法,没有编译。看到重复并将其转换为 DP 是相当简单的。很快就会发布。

【讨论】:

    【解决方案4】:

    奇怪的 Prologesque 伪代码中的递归答案:

    maxSum([]) = 0
    maxSum([x]) = x
    maxSum(A) = max(A[0] + maxSum(A[2..n]),
                    A[1] + maxSum(A[3..n]))
    

    适当处理超出范围的索引。

    编辑:这简化为 MarcusQ 更好的答案:

    maxSum([]) = 0
    maxSum(A) = max(A[0] + maxSum(A[2..n]), maxSum(A[1..n]))
    

    编辑:这是一个返回实际子序列而不仅仅是总和的版本。它扩展了我的临时伪 Prolog-C Chimera 的限制,所以我现在就停下来。

    maxSub([]) = []
    maxSub(A) = sub1 = [A[0]] + maxSub(A[2..n])
                sub2 = maxSub(A[1..n])
                return sum(sub1) > sum(sub2) ? sub1 : sub2
    

    【讨论】:

    • OP 询问最大 子序列,而不仅仅是它的总和。
    • 是的,问题的标题是这样说的,但我从实际问题中推断出 OP 只对总和感兴趣。扩展此算法以返回子序列及其总和是微不足道的,但看起来不太好。
    • 如果[] 不表示伪Prolog-C 中的链表,那么[A[0]] + maxSub(A[2..n]) 是O(N) 操作如果它涉及创建一个新的子序列(如果它不那么alg 不起作用)。
    • 一切都意味着具有值语义,因此 [] + [] 返回一个新数组(列表,随便)。很明显,效率不是伪 Prolog-C 伪编码器的考虑因素。我试图简短而清晰地介绍算法,但也许我没有成功。
    • 我更喜欢用可执行的伪代码来交流算法,但那样我可能会很奇怪。 10 个中只有 1 个(不包括我的)其他答案包含该问题的可执行示例。
    【解决方案5】:

    @MarkusQ's answer 作为 Python oneliner(修改为 cmets 中建议的 @recursive):

    f = lambda L: L and max([L[0]] + f(L[2:]), f(L[1:]), key=sum)
    

    例子:

    >>> f([1,51,3,1,100,199,3])
    [51, 1, 199]
    

    效率不高,但可以用来测试更快的解决方案。

    相同 -- 在 Emacs Lisp 中

    (defun maxsubseq (L)
      "Based on MarkusQ's and sth's answers."
      (if (not L) L
        (let ((incl (cons (car L) (maxsubseq (cddr L))))
              (excl (maxsubseq (cdr L))))
          (if (> (sum incl) (sum excl)) incl excl))))
    
    (defun sum (L) (apply '+ L))

    迭代版本(如果尾递归可用,则为 O(N))

    它基于@sth's answer:

    (defun maxsubseq-iter-impl (L excl incl) (let ((next (if (> (car excl) (car incl)) excl incl)) (x (car L))) (如果(不是 L)(下一个 cdr) (maxsubseq-iter-impl (cdr L) 下一个 (缺点 (+ x (汽车除外)) (缺点 x (cdr 除外))))))) (defun maxsubseq-iter (L) (反向 (maxsubseq-iter-impl L '(0) '(0))))

    示例:

    (需要'cl) (for f in '(maxsubseq maxsubseq-iter) 收集(L in '((1 51 3 1 100 199 3) (9 10 9)) 循环 收集 (f L)))

    输出:

    (((51 1 199) (9 9)) ((51 1 199) (9 9)))
    

    【讨论】:

    • 稍短 f = lambda L: L and max([L[0]] + f(L[2:]), f(L[1:]), key=sum)跨度>
    【解决方案6】:

    这是一个使用动态编程完成的答案,使用的基本概念与 MarkusQ 使用的基本概念相同。我只是在计算总和,而不是实际的序列,它可以通过对这个代码示例的简单修改来产生。我很惊讶没有人提到这一点,因为动态编程似乎比递归 + 记忆更好!

    int maxSeqSum(int *arr, int size) {
      int i, a, b, c;
      b = arr[0];
      a = (arr[1] > arr[0]) ? arr[1]: arr[0];
      for(i=2;i<size;i++) {
        c = (a > (b + arr[i]))? a : (b + arr[i]);
        b = a;
        a = c;
      }
      return a;
    }
    

    【讨论】:

      【解决方案7】:

      max(oddIndexSum,evenIndexSum) 不起作用

      对于您给出的示例,确实如此 - 但是,如果您有类似的内容:A = [1, 51, 3, 2, 41, 23, 20],您可以使用 51 + 2 + 23 = 76,或者您可以使用 51 + 41 + 20 = 112,显然更大,并且也避免了相邻元素。这是你要找的吗?

      【讨论】:

      • 我应该说 max(oddIndexSum,evenIndexSum) 在所有情况下都不起作用。我的坏...
      【解决方案8】:

      虽然你用了一堆花哨的词,但这基本上只是一个普通的旅行推销员的老图问题吗?

      除了在这种情况下,您正在寻找通过(密集)图的最昂贵的路线吗?在这种情况下,顶点只是数字本身,边没有方向且没有权重,并且所有顶点都是连接的,除了在原始列表中与它们相邻的顶点?

      【讨论】:

      • @Joe: 1) 是的,看起来问题可以归结为专门的 TSP 问题。谢谢 ! 2)你指出我使用了“一堆花哨的词”,这表明我试图表现得比我更聪明(下意识地,我保证!)。再次感谢!
      • 这与这个专门的 TSP 不同。使用 [5, 6, 7, 8, 9],旅行商可以走 5-8-6-9;但不能选择 5、6、8、9 作为子序列。在这个问题中节点是有序的,但它们不适用于 TSP。
      • @Joe:不,这不是 TSP——这个问题可以在线性时间内解决,而 TSP 是 > 多项式。 -- MarkusQ
      • 它不是 TSP。例如,@sth 的答案显示时间为 O(N**2),空间算法为 O(N)。
      • 天啊!谢谢大家-我确实错过了一些关键要素。不应该这么着急。 :) 我很惊讶它可以在 O(N) 内完成 - 我会去看看。
      【解决方案9】:

      编辑:这确实是一个骗局,但直到我发布后才意识到。

      假设您不需要跟踪哪些项目对最终总和有贡献,您可以在恒定空间和线性时间中执行此操作。

      伪代码:

      sum_excluded_last_item= 0
      sum_included_last_item= 0
      
      for each item in list
          if (item>0)
              last_sum_excluded_last_item= sum_excluded_last_item
              sum_excluded_last_item= max(sum_included_last_item, sum_excluded_last_item + item)
              sum_included_last_item= last_sum_excluded_last_item + item
          else
              sum_excluded_last_item= max(sum_excluded_last_item, sum_included_last_item)
              sum_included_last_item= sum_excluded_last_item
      
      max_sum= max(sum_excluded_last_item, sum_included_last_item)
      

      获取实际列表是留给读者的练习。或者我,如果你添加更多的 cmets。但是从算法上应该是显而易见的。

      【讨论】:

        【解决方案10】:

        为了避免递归,我们可以从反向而不是正向,

        ie) 用于数组 A[1..n]->

             maxSum(A,n): for all n
        
                 if n=0, maxSum = 0 else
                 if n=1, maxSum=A[1] else
                        maxSum = max(A[n] + maxSum(A,n-2), maxSum(A,n-1))
        

        为了避免计算Max(A,n-2),在扩展maxSum(A,n-1)的同时,可以存储和计算。这就是为什么我要求扭转。即) maxSum(A,n-1) = max(A[n-1]+ maxSum(A,n-3), maxSum(A,n-2) ) 其中 Max(A,n-2) 已经得到,无需重新计算)换句话说,使用上述公式计算从 1 到 n 的所有 n 的 maxSum(A,n) 以避免重新计算。

        ie) n=2, maxSum = max(A[1]+maxSum(A,0), maxSum(A,1) ) 即)n=3, maxSum = max(A[2]+maxSum(A,2), maxSum(A,2) ) 等等..并到达最后一个 n。 这将是 o(n)。

        【讨论】:

        • 这是一个伪代码,我们可以在计算 maxsum(A,n-1) 之前存储 MaxSum(A,n-2),我们可以取到 O(n)
        【解决方案11】:

        我们可以使用辅助数组 B[0..n-1],其中 B[i] 是元素 A[0..i] 和 C[0..n-1] 的最大和,其中C[i] 是布尔值,判断 A[i] 是否在最大和子序列中:

        B[0]=max(A[0],0); C[0]=(A[0]>0)
        B[1]=max(B[0],A[1]); C[1]=(A[1]>B[0])
        for i in [2..n-1]
          if A[i]+B[i-2] > B[i-1]
              C[i]=True
              B[i]=A[i]+B[i-2]
          else
              C[i]=False
              B[i]=B[i-1]
        mssq=[]
        i=n-1
        while i>=0
          if C[i]
            push(A[i],mssq)
            i=i-2
          else
            i=i-1
        return mssq
        

        这显然适用于 O(n) 时间和空间。其实这和MarcusQ的方案是一样的,只是逆向优化了。

        【讨论】:

        • 问题是关于最大子序列,而不是它的总和。如果您想留在 O(N) 中,这不是一个微不足道的变化。
        • 考虑这个序列 [ 5 4 3 4 5 4 3 4 ],现在最大和为 17。可以使用 [5 4 4 4] 和 [5 3 5 4] 形成,但使用您的算法我们只能得到 [5 4 4 4] 而不是其他组合。知道怎么改吗?
        【解决方案12】:

        MarkusQ 的代码似乎完全跳过了 a[2]。我不够聪明,无法弄清楚它应该在哪里计算。

        【讨论】:

          【解决方案13】:
          while you still have elements
               find the largest element, add it to the sum
               remove the element before and after the current
          

          【讨论】:

          • 这在一个简单的情况下失败:[2, 3, 2]。
          猜你喜欢
          • 2012-04-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-04-19
          • 1970-01-01
          • 1970-01-01
          • 2017-01-17
          相关资源
          最近更新 更多