是否可以使用恒定的额外空间反转数组?

时间:2015-08-02 15:05:34

标签: arrays algorithm inverse

假设我在 [0,n] 范围内有一个 A 数组,其中包含 n 个唯一元素。换句话说,我有整数[0,n]的排列。

可以使用O(1)额外空间(就地使用AKA)将 A 转换为 B ,这样 B [A [i]] =我

例如:

       A                  B
[3, 1, 0, 2, 4] -> [2, 1, 3, 0, 4]

4 个答案:

答案 0 :(得分:24)

是的,有可能,使用O(n ^ 2)时间算法:

将索引为0的元素,然后将0写入该元素索引的单元格。然后使用刚覆盖的元素来获取下一个索引并在那里写入前一个索引。继续,直到返回索引0.这是循环领导算法。

然后从索引1,2开始做同样的事情......但是在进行任何更改之前执行循环引导算法而不从该索引开始进行任何修改。如果此循环包含低于起始索引的任何索引,则跳过它。

或者这个O(n ^ 3)时间算法:

将索引为0的元素,然后将0写入该元素索引的单元格。然后使用刚覆盖的元素来获取下一个索引并在那里写入前一个索引。继续,直到你回到索引0。

然后从索引1,2开始做同样的事情......但是在进行任何更改之前执行循环引导算法而不从所有前面的索引开始进行任何修改。如果任何前一个周期中存在当前索引,则跳过它。

我在C ++ 11中编写了(略微优化的)implementation O(n ^ 2)算法,以确定如果随机置换被反转,平均每个元素需要多少次访问。结果如下:

size accesses
2^10 2.76172
2^12 4.77271
2^14 6.36212
2^16 7.10641
2^18 9.05811
2^20 10.3053
2^22 11.6851
2^24 12.6975
2^26 14.6125
2^28 16.0617

当大小呈指数级增长时,元素访问次数几乎呈线性增长,因此随机排列的预期时间复杂度类似于O(n log n)。

答案 1 :(得分:0)

反转数组A要求我们找到符合所有B要求A[B[i]] == i的排列i

要构建反向就地,我们必须通过为每个元素A[A[i]] = i设置A[i]来交换元素和索引。显然,如果我们只是遍历A并执行上述替换,我们可能会覆盖A中的即将到来的元素,我们的计算将会失败。

因此,我们必须遵循Ac = A[c]的周期交换元素和索引,直到我们达到周期的起始索引c = i

A的每个元素都属于这样一个循环。由于我们没有空间来存储元素A[i]是否已经被处理并且需要被跳过,我们必须遵循它的循环:如果我们到达索引c < i,我们就会知道这个元素是以前处理过的循环的一部分。

此算法的最差情况下运行时复杂度为 O(n²),平均运行时复杂度为 O(n log n)且最佳-case运行时复杂度 O(n)

function invert(array) {
  main:
  for (var i = 0, length = array.length; i < length; ++i) {
    
    // check if this cycle has already been traversed before:
    for (var c = array[i]; c != i; c = array[c]) {
      if (c <= i) continue main;
    }
    
    // Replacing each cycle element with its predecessors index:
    var c_index = i, 
        c = array[i];
    do {
      var tmp = array[c];
      array[c] = c_index; // replace
      c_index = c; // move forward
      c = tmp;
    } while (i != c_index)
      
  }
  return array;
}
  
console.log(invert([3, 1, 0, 2, 4])); // [2, 1, 3, 0, 4]

示例 A = [1, 2, 3, 0]

  1. 索引 0 的第一个元素 1 属于元素1 - 2 - 3 - 0的循环。一旦我们移动索引0,1,2在这个循环中,我们完成了第一步。

  2. 索引 1 的下一个元素 0 属于同一个周期,我们的检查只在一个步骤中告诉我们(因为它是一个向后的步骤)

  3. 其余元素 1 2 也是如此。

    enter image description here

  4. 总的来说,我们执行4 + 1 + 1 + 1'操作'。这是最好的情况。

答案 2 :(得分:0)

explanation在Python中的实现:

def inverse_permutation_zero_based(A):
    """
    Swap elements and indices along cycles of A by following `c = A[c]` until we reach
    our cycle's starting index `c = i`.

    Every element of A belongs to one such cycle. Since we have no space to store
    whether or not an element A[i] has already been processed and needs to be skipped,
    we have to follow its cycle: If we reach an index c < i we would know that this
    element is part of a previously processed cycle.

    Time Complexity: O(n*n), Space Complexity: O(1)
    """

    def cycle(i, A):
        """
        Replacing each cycle element with its predecessors index
        """
        c_index = i
        c = A[i]

        while True:
            temp = A[c]
            A[c] = c_index  # replace
            c_index = c  # move forward
            c = temp

            if i == c_index:
                break

    for i in range(len(A)):
        # check if this cycle has already been traversed before
        j = A[i]

        while j != i:
            if j <= i:
                break
            j = A[j]
        else:
            cycle(i, A)

    return A

>>> inverse_permutation_zero_based([3, 1, 0, 2, 4])
[2, 1, 3, 0, 4]

答案 3 :(得分:0)

如果我们尝试在一个位置存储 2 个数字,这可以在 O(n) 时间复杂度和 O(1) 空间内完成。

First, let's see how we can get 2 values from a single variable. Suppose we have a variable x and we want to get two values from it, 2 and 1. So,
 
x = n*1 + 2 , suppose n = 5 here.
x = 5*1 + 2 = 7
Now for 2, we can take remainder of x, ie, x%5. And for 1, we can take quotient of x, ie , x/5

and if we take n = 3
x = 3*1 + 2 = 5

x%3 = 5%3 = 2
x/3 = 5/3 = 1

我们在这里知道数组包含范围 [0, n-1] 内的值,因此我们可以将除数取为 n,数组的大小。因此,我们将使用上述概念在每个索引处存储 2 个数字,一个表示旧值,另一个表示新值。

  A                   B

 0  1  2  3  4      0  1  2  3  4    
[3, 1, 0, 2, 4] -> [2, 1, 3, 0, 4]

.

a[0] = 3, that means, a[3] = 0 in our answer.
a[a[0]] = 2 //old
a[a[0]] = 0 //new
a[a[0]] = n* new + old = 5*0 + 2 = 2

a[a[i]] = n*i + a[a[i]]

并且在数组遍历期间,a[i] 值可能大于 n,因为我们正在修改它。所以我们将使用 a[i]%n 来获取旧值。 所以逻辑应该是

a[a[i]%n] = n*i + a[a[i]%n]

Array -> 13 6 15 2 24

现在,要获得旧值,将每个值除以 n 取余数,要获得新值,只需将每个值除以 n,在本例中,n=5。

Array -> 2 1 3 0 4