在 O(nlogn) 时间复杂度中找到总和为 0 的子数组(使用分而治之)?

Posted

技术标签:

【中文标题】在 O(nlogn) 时间复杂度中找到总和为 0 的子数组(使用分而治之)?【英文标题】:Find a subarray with sum 0 in O(nlogn) time complexity (Using Divide and Conquer)? 【发布时间】:2020-10-03 19:22:03 【问题描述】:

我在网上看到过解决方案,但所有解决方案都有 O(n) 或 O(n^2) 时间复杂度。我想知道是否可以在不使用辅助数据结构的 O(nlogn) 中找到总和为 0 的子数组。但是,我们可以使用递归。

我们可以修改最大子数组求和算法来解决这个问题吗?

输入数组将只有 1 和 -1,算法将找到总和为 0 的子数组。

输入 = 1, 1, -1, 1, -1, -1, 1, -1

output = 1, 8(1 为起始索引,8 为最后索引)

在这种特殊情况下,整个输入数组的和等于 0。因此,报告的开始和结束索引分别为 1 和 8(假设数组中的索引从 1 开始)。

编辑:我们可以使用这个问题的解决方案来解决另一个问题。问题如下。

给定一个包含 n 个整数的数组 arr,找到最长的连续子数组,其中偶数和奇数元素的数量相等。下面是一个例子(索引从 1 开始):

A = 8, 2, -3, 4, 9, 6

答案:(2, 5)。 (2 为起始索引,5 为最后索引)

唯一的限制是算法不能使用任何辅助数据结构。在这种约束下,解决方案必须是最有效的。此外,允许使用递归。

【问题讨论】:

可能比O(n)更好是不可能的,你必须至少扫描一次数组。 @ZabirAlNazi,他并没有要求更好。 对不起,我完全错过了。 一定要分而治之吗?它可以使用递归(以及递归后使用的空间)吗?可以是 O(n) 吗? 限制是不能使用任何辅助数据结构。它不必分而治之。它只需要在不使用辅助数据结构的约束下尽可能快。 【参考方案1】:

您可以使用递归算法,其中函数获取前一个数组值(如果有)的值,然后从输入数组中读取下一个值。如果它是相同的值,那么它会递归地调用自己,当从那里返回时,它会以相同的方式继续下一个值。如果它是相反的值,它会返回给调用者true——表明存在一个零和。如果遇到数组末尾,函数返回false

这实际上意味着递归的深度等于绝对累积和。因此,例如,如果数组是 [-1, -1, -1, 1],则递归将进入深度 3,它将从第 3 级返回到第 2 级,返回值 true。在第 2 级,它将返回 false,因为遇到数组的末尾,因此它将退出递归。

只要返回值为true,您就可以检查所覆盖的区间是否比目前遇到的更大。

这是这个想法在 javascript 中的实现:

function zeroSum(arr) 
  let i = 0; // index in the input array, managed outside of recursion
  // Longest zero-sum interval so far. Zero based, and value at end index 
  //   is not part of the interval:
  let longest = [0, 0];

  function recur(dir)  // dir is the previous value from the array (if any)
    let start = i; // local variable
    while (i < arr.length) 
      let val = arr[i++];
      if (val == -dir) return true; // zero sum
      if (recur(val) && i - start > longest[1] - longest[0]) 
        longest[0] = start;
        longest[1] = i;
      
    
    return false; // no zero sum
  

  recur(0); // 0 is passed to indicate there is no previous value
  return longest;


// demo
console.log(zeroSum([1, 1, -1, 1, -1, -1, 1, -1]));

【讨论】:

我已经运行了代码,我认为最长数组中的第二个元素的值是 1。这意味着您输入的数组的答案应该是 [0, 7] 因为在您的解决方案中索引从 0 开始,但返回的答案是 [0,8]。除此之外,它工作得很好。此外,有没有一种方法可以使用您的算法来找到具有相同几率甚至其中的子数组。我认为数组中的 1 代表偶数,-1 代表奇数。你认为你可以修改你的解决方案来解决这个问题吗? 本来就是这样的。在我提到的 cmets 中,结束索引处的值不是范围的一部分。这是常见的做法(参见 slice 方法在 JavaScript 中的工作方式,或 range 函数在 Python 中的工作方式)。这样,两个索引的减法就可以为您提供范围的大小。至于修改奇数/偶数的解决方案。请尝试一下,自己做这件事并不难。提示:你只需要修改这一行:let val = arr[i++]; 好的。知道了。谢谢先生。将修改它以适用于奇数和偶数的数组。最后,您认为您的解决方案的时间和空间复杂度是多少? 你自己怎么看? :) 提示:let val = arr[i++]; 总共执行了多少次(假设只有一个 i 变量并且它在其他任何地方都没有改变)? 时间复杂度为 O(n)。在最坏的情况下,空间复杂度为 O(n)。例如,当列表不包含任何 -1 时,递归将与列表一样长。在这种情况下,使用的堆栈空间是 O(n)。这回答了你的问题吗?

以上是关于在 O(nlogn) 时间复杂度中找到总和为 0 的子数组(使用分而治之)?的主要内容,如果未能解决你的问题,请参考以下文章

O(nlogn) + O(n) 的时间复杂度是不是只是 O(nlogn)?

排序时间复杂度为O(nlogn)的排序算法

所有数字的总和,直到它在java中变成单个数字,复杂度为o(1)?

比较排序算法复杂度

如何证明以下算法具有 O(nlogn) 时间复杂度

O(1), O(n), O(logn), O(nlogn) 的区别