简体   繁体   English

该算法获得数组的所有子 arrays 除以给定数字的时间/空间复杂度是多少

[英]What is the time/space complexity of this algorithm to get all the sub arrays of an array divided by a given number

I am writing a function that takes an array and an integer number and returns an array of subarrays.我正在编写一个 function ,它接受一个数组和一个 integer 数字并返回一个子数组数组。 The number of subarrays is exact the integer number passed to the function.子阵列的数量与传递给 function 的 integer 数量完全相同。 And the subarrays have to be continuous, meaning the original order of items in the array has to be preserved.并且子数组必须是连续的,这意味着必须保留数组中项目的原始顺序。 Also no subarray can be empty.也没有子数组可以为空。 They have to have at least one item in it.他们必须至少有一个项目。 For example:例如:

const array = [2,3,5,4]
const numOfSubarray = 3

const subarrays = getSubarrays(arraym numOfSubarray)

In this case subarrays is this:在这种情况下, subarrays是这样的:

[
  [[2, 3], [5], [4]],
  [[2], [3, 5], [4]],
  [[2], [3], [5, 4]],
]

Here is my attempt:这是我的尝试:

function getSubarrays(array, numOfSubarray) {
  const results = []

  const recurse = (index, subArrays) => {
    if (index === array.length && subArrays.length === numOfSubarray) {
      results.push([...subArrays])
      return
    }
    if (index === array.length) return

    // 1. push current item to the current subarray
    // when the remaining items are more than the remaining sub arrays needed

    if (array.length - index - 1 >= numOfSubarray - subArrays.length) {
      recurse(
        index + 1,
        subArrays.slice(0, -1).concat([subArrays.at(-1).concat(array[index])])
      )
    }
    // 2. start a new subarray when the current subarray is not empty

    if (subArrays.at(-1).length !== 0)
      recurse(index + 1, subArrays.concat([[array[index]]]))
  }

  recurse(0, [[]], 0)
  return results
}

Right now it seems to be working.现在它似乎正在工作。 But I wanted to know what is the time/space complexity of this algorithm.但我想知道这个算法的时间/空间复杂度是多少。 I think it is definitely slower than O(2^n) .我认为它肯定比O(2^n)慢。 Is there any way to improve it?有什么办法可以改善吗? Or any other solutions we can use to improve the algorithm here?或者我们可以用来改进算法的任何其他解决方案?

You could take a recursive approach.您可以采用递归方法。

The time complecity is the binomial coefficient : array.length - 1 choose length - 1 because of unwanted empty items.时间复杂度是二项式系数array.length - 1选择length - 1因为不需要的空项目。

 function getSubarrays(array, length) { if (length === 1) return [[array]]; const result = []; let i = array.length - length + 1; do result.push(...getSubarrays(array.slice(i), length - 1).map(a => [array.slice(0, i), ...a])); while (--i) return result; } console.log(JSON.stringify(getSubarrays([2, 3, 5, 4], 3))); console.log(JSON.stringify(getSubarrays([2, 3, 5, 4, 5], 3)));

The problem can be solved by a different approach:该问题可以通过不同的方法解决:

  • compute all the combinations of numOfSubarray numbers ranging from 1 to the length of array计算从 1 到array长度的numOfSubarray数字的所有组合
  • each combination is a valid slicing of array .每个组合都是array的有效切片。 For instance, you want to slice the array in your example at positions (1, 2), (1, 3), (2, 3), yielding subarrays [[2],[3],[5,4]] , [[2],[3,5],[4]] , and [[2,3],[5],[4]]例如,您想在示例中的位置 (1, 2)、(1, 3)、(2, 3) 处对数组进行切片,产生子数组[[2],[3],[5,4]][[2],[3,5],[4]][[2,3],[5],[4]]

Time complexity is, I believe, O(r(nCr)) where n is (length of array - 1) and r is (number of subarrays - 1).我相信时间复杂度是 O(r(nCr)),其中 n 是(数组的长度 - 1),r 是(子数组的数量 - 1)。

To visualize how and why it works have a look at the stars and bars technique要可视化它的工作方式和原因,请查看星条技术

If you want to completely split a list of n elements into k disjunct, continuous sub-lists this is like placing k-1 split points into the n-1 gaps between the elements:如果要将n元素的列表完全拆分为k个分离的连续子列表,这就像将k-1拆分点放入元素之间的n-1间隙中:

2 | 3 | 5   4
2 | 3   5 | 4
2   3 | 5 | 4

In combinatorics this is taking k-1 from n-1 .在组合学中,这是从n-1中获取k-1 1 。 So I think the result size of the ouput will be n-1 take k-1 = (n-1)! / ((k-1)! * (nk)!)所以我认为输出的结果大小将是n-1 take k-1 = (n-1)! / ((k-1)! * (nk)!) n-1 take k-1 = (n-1)! / ((k-1)! * (nk)!) . n-1 take k-1 = (n-1)! / ((k-1)! * (nk)!) . Thus the complexity is something polynomial like O(n^(k-1)) for constant k .因此,复杂性是多项式,例如常数k O(n^(k-1)) If you don't fix k but raise it with n like k = n/2 the complexity will get exponential.如果你不修复k而是用n提高它,比如k = n/2 ,复杂性将呈指数增长。

I don't think that you can improve this, because the output's size is increasing by this complexity.我不认为你可以改进这一点,因为输出的大小正在增加这种复杂性。

tl;dr tl;博士

The number of solutions is bound to (as @gimix mentioned) binomial coefficient, so if I understand correctly it's pessimistically exponential https://en.wikipedia.org/wiki/Binomial_coefficient#Bounds_and_asymptotic_formulas .解决方案的数量与(如@gimix提到的)二项式系数有关,所以如果我理解正确,它是悲观的指数https://en.wikipedia.org/wiki/Binomial_coefficient#Bounds_and_asymptotic_formulas

If I'm not mistaken that makes your algorithm this exponential * n (for each element of each solution) * n (because on nearly every step you copy array which length might be dependent on n ).如果我没记错的话,这会使您的算法成为指数 * n (对于每个解决方案的每个元素) * n (因为几乎在每个步骤中,您都复制了长度可能取决于n的数组)。

  • fix second if - only call recurse if subArrays.length < numOfSubarrays修复第二个 if - 仅在 subArrays.length < numOfSubarrays 时调用递归
  • you are copying arrays a lot - slice, concat, spread operator - all of those create new arrays.您正在大量复制 arrays - slice、concat、spread 运算符 - 所有这些都会创建新的 arrays。 If for every solution (which length might be depending on n ) on every step you copy this solution (which I think is happening here) you multiply the complexity by n .如果对于每个解决方案(其长度可能取决于n )在每个步骤中复制此解决方案(我认为在这里发生),您将复杂性乘以n
  • the space complexity is also exponential * n - you store the exponential number of solutions, possibly of length dependent on n .空间复杂度也是指数 * n - 您存储指数数量的解,可能长度取决于n Using a generator and returning one solution at the time could greatly improve that.使用生成器并在当时返回一个解决方案可以大大改善这一点。 As @gimix mentioned the combinations might be the simplest way to do it.正如@gimix 提到的那样,组合可能是最简单的方法。 Combinations generator in python: https://docs.python.org/3/library/itertools.html#itertools.combinations python 中的组合生成器: https://docs.python.org/3/library/itertools.html#itertools.combinations

Dwelling on complexity:考虑复杂性:

I think you are right about the slower than exponential complexity, but - bare with me - how much do you know about Fibonacci's sequence?我认为你对慢于指数复杂性的看法是正确的,但是 - 对我来说 - 你对斐波那契数列了解多少? ;) ;)

Let's consider input:让我们考虑输入:

array = [1, 2, ..., n]
numOfSubarrays = 1

We can consider the recursive calls a binary tree with if 1. guarding the left child (first recurse call) and if 2. guarding the right child (second recurse call).我们可以考虑递归调用一棵二叉树, if 1. 保护左孩子(第一次recurse调用), if 2. 保护右孩子(第二次recurse调用)。

For each recurse call if 1. will be fulfilled - there are more items than sub arrays needed.对于每个递归调用if 1. 将被满足 - 项目比子 arrays 需要的多。

Second if will be true only if current sub array has some elements.第二个if仅当当前子数组有一些元素时才为真。 It's a tricky condition - it fails if, and only if, it succeeded one frame higher - an empty array has been added at the very beginning (except for the root call - it has no parent).这是一个棘手的条件 - 当且仅当它成功高一帧时它才会失败 - 一开始就添加了一个空数组(根调用除外 - 它没有父级)。 Speaking in terms of a tree, it means we are in the right child - the parent must have just added an empty sub array as a current.就树而言,这意味着我们在正确的孩子中 - 父母必须刚刚添加了一个空子数组作为当前。 On the other hand, for the left child parent has just pushed (yet another?) element to the current sub array and we are sure the if 2. will succeed.另一方面,对于左孩子,父母刚刚将(又一个?)元素推送到当前子数组,我们确信 if 2. 会成功。

Okay, but what does it say about the complexity?好的,但它说明了复杂性是什么?

Well, we can count the number of nodes in the tree, multiply by the number of operations they perform - most of them a constant number - and we get the complexity.好吧,我们可以计算树中节点的数量,乘以它们执行的操作的数量——其中大多数是一个常数——我们就得到了复杂度。 So how many are there?那么有多少呢?

I'll keep track of left and right nodes separately on every level.我将在每个级别上分别跟踪左右节点。 It's gonna be useful soon.很快就会有用的。 For convenience I'll ignore root call (I could treat it as a right node - it has empty sub array - but it messes up the final effect) and start from level 1 - the left child of the root call.为方便起见,我将忽略根调用(我可以将其视为右节点 - 它具有空子数组 - 但它会破坏最终效果)并从级别 1 开始 - 根调用的左子节点。

r 1 = 0 r 1 = 0
l 1 = 1 l 1 = 1

As a left node (sub array isn't empty) it has two children:作为左节点(子数组不为空),它有两个子节点:

r 2 = 1 r 2 = 1
l 2 = 1 l 2 = 1

Now, the left node always has two children (1. is always fulfilled; 2. is true because parent pushed element to current sub array) and the right node has only the left child:现在,左节点总是有两个子节点(1. 总是满足;2. 为真,因为父节点将元素推送到当前子数组),右节点只有左子节点:

r 3 = r 2 + l 2 = 1 + 1 = 2 r 3 = r 2 + l 2 = 1 + 1 = 2
l 3 = r 2 = 1 l 3 = r 2 = 1

we could continue.我们可以继续。 The results are:结果是:

l l r r
1 1 0 0
1 1 1 1
2 2 1 1
3 3 2 2
5 5 3 3

well... it's oddly familiar, isn't it?嗯……奇怪的熟悉,不是吗?

Okay, so apparently, the complexity is O(Σ(F i + F i-1 ) where 1 <= i <= n).好吧,很明显,复杂度是 O(Σ(F i + F i-1 ),其中 1 <= i <= n)。

Alright, but what does it really mean?好吧,但它的真正含义是什么? There is a very cool prove that S(n) - sum of the Fibonacci numbers from 0 to n is equal F(n+2) - 1. It simplifies the complexity to:有一个非常酷的证明S(n) - 从 0 到 n 的斐波那契数之和等于 F(n+2) - 1。它将复杂性简化为:

O(S(n) + S(n-1)) = O(F(n+2) - 1 + F(n+1) - 1) = O(F(n+3) - 2) = O(F(n+3))

We can forget about the +3 since F(n+3) < 2 * F(n+2) < 4 * F(n+1) < 8 * F(n).我们可以忘记 +3,因为 F(n+3) < 2 * F(n+2) < 4 * F(n+1) < 8 * F(n)。

The final question, is Fibonacci sequence exponential?最后一个问题,斐波那契数列是指数的吗? Yes and no apparently.是的,显然不是。 The is no number that would fulfil the x n = F(n) - the value oscillates between 2 and √2, because for F(n+1) < 2 * F(n) < F(n+2).不是可以满足 x n = F(n) 的数字 - 该值在 2 和 √2 之间波动,因为对于 F(n+1) < 2 * F(n) < F(n+2)。

It's proven though, that lim(n->∞) F(n+1) / F(n) = φ - the golden ratio.但事实证明,lim(n->∞) F(n+1) / F(n) = φ - 黄金比例。 It means the O(F(n)) = O(φ n ).这意味着 O(F(n)) = O(φ n )。 (Actually, you copy arrays a lot, so it's more like O(φ n *n)) (其实你复制arrays很多,所以更像O(φ n *n))

How to fix it?如何解决? You could check if there isn't too many arrays before recursing in if 2.您可以在 if 2 递归之前检查是否没有太多 arrays 。

Other than that, just as @Markus mentioned, depending on the input, the number of solutions might be exponential, so the algorithm to get them also has to be exponential.除此之外,正如@Markus 提到的,根据输入,解决方案的数量可能是指数的,因此获得它们的算法也必须是指数的。 But that's not true for every input, so let's keep those cases to minimum:D但这并非对每个输入都是如此,所以让我们将这些情况保持在最低限度:D

You can't get an answer down to anything like 2 n , I'm afraid.恐怕你无法得到像2 n这样的答案。 This grows much faster than that, because the answer has to do with the binomial coefficients, whose definitions have fundamental factorial parts, and whose approximations involve terms like n n .这比这增长得快得多,因为答案与二项式系数有关,其定义具有基本的阶乘部分,并且其近似值涉及n n之类的项。

Your solution seems likely to be worse than necessary, noted because of the exponential number of calls required to solve the simplest case, when numOfSubarrays is 1 , and you should just be able to return [array] .您的解决方案似乎比必要的更糟,因为解决最简单情况所需的调用次数呈指数级,当numOfSubarrays1时,您应该能够返回[array] But as to full analysis, I'm not certain.但至于全面分析,我不确定。

However, if your're interested in another approach, here's how I might do it, based on the same insight others have mentioned, that the way to do this is to find all sets of numOfSubarrays indices of the positions between your values, and then convert them to your final format:但是,如果您对另一种方法感兴趣,我可能会这样做,基于其他人提到的相同见解,这样做的方法是找到您的值之间位置的所有numOfSubarrays索引集,然后将它们转换为您的最终格式:

 const choose = (n, k) => k == 0? [[]]: n == 0? []: [... choose (n - 1, k), ... choose (n - 1, k - 1). map (xs => [...xs, n])] const breakAt = (xs) => (ns) => [...ns, xs.length].map ((n, i) => xs.slice (i == 0? 0: ns [i - 1], n)) const subarrays = (xs, n) => choose (xs.length - 1, n - 1).map (breakAt (xs)) console.log (subarrays ([2, 3, 5, 4], 3) // combine for easier demo.map (xs => xs.map (ys => ys.join ('')).join('-')) //=> ["23-5-4", "2-35-4", "2-3-54"] ) console.log (subarrays ([2, 3, 5, 4], 3)) // pure result
 .as-console-wrapper {max-height: 100%;important: top: 0}

Here, choose (n, k) finds all the possible ways to choose k elements from the numbers 1 , 2 , 3 , ..., n .在这里, choose (n, k)找到从数字123 、 ...、 n中选择k个元素的所有可能方法。 So, for instance, choose (4, 2) would yield [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] .因此,例如, choose (4, 2)将产生[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] .

breakAt breaks an array into sub-arrays at a set of indices. breakAt将数组分解为一组索引处的子数组。 So所以

breakAt ([8, 6, 7, 5, 3, 0, 9]) ([3, 5])
//                3     5  ///=> [[8, 6, 7], [5, 3], [0, 9]]

And subarrays simply combines these, subtracting one from the array length, subtracting one from numOfSubarrays , calling choose with those two values, and then for each result, calling breakAt with the original array and this set of indices. subarrays数组只是将这些组合起来,从数组长度中减去一个,从numOfSubarrays中减去一个,使用这两个值调用choose ,然后对于每个结果,使用原始数组和这组索引调用breakAt

Even here I haven't tried to analyze the complexity, but since the output is factorial, the process will take a factorial amount of time.即使在这里我也没有尝试分析复杂性,但由于 output 是阶乘,因此该过程将花费阶乘时间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 该算法的时间复杂度是多少? - What is the time complexity of this algorithm? 以下算法的3Sum问题的时空复杂度是多少? - What is the Time and Space Complexity of the 3Sum problem with the following algorithm? ES6阵列交换的时间/空间复杂度是多少? - What is the time/space complexity of ES6 array swap? 使用while循环来压缩排序数组的时间和空间复杂度是多少? - What is time and space complexity for concating sorted array by using while loop? 这个 function 的时间和空间复杂度是多少? - What is time & space complexity of this function? 将两个已排序的 arrays 合并为更大的排序数组的时间复杂度是多少? - What is the time complexity of merging two sorted arrays into a larger sorted array? 如何确定该算法的时间和空间复杂度? - How to determine the time and space complexity of this algorithm? Javascript - 在时间复杂度为 O(n) 且空间复杂度为 O(1) 的给定字符串中删除交替重复字符的最佳方法是什么? - Javascript - What is the best way to remove alternate repeating character in a given string with time complexity O(n) and Space Complexity was O(1)? 这个素因子算法的时间复杂度是多少 - What is the time complexity of this prime factors algorithm 我的排序算法的时间复杂度应该是多少? - What should be the time complexity of my sorting algorithm?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM