【问题标题】:Is it possible to invert an array with constant extra space?是否可以反转具有恒定额外空间的数组?
【发布时间】:2015-08-02 15:05:33
【问题描述】:

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

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

例如:

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

【问题讨论】:

  • 我们可以使用您的数组条目的符号位来编码信息吗?还是会违背不使用额外空间的想法?
  • @NiklasB。这将是每个条目 1 位 - O(n) 空间。不允许。
  • 这真的取决于型号。例如,在经典 RAM 模型中,我们有 log n
  • @NiklasB。如果你想具体一点,transdichotomous model.
  • 这听起来好像更适合Computer Science

标签: arrays algorithm inverse


【解决方案1】:

是的,有可能,使用 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)。

【讨论】:

  • 有一个 Theta(n log n) 预期复杂性的证明,我们表明迭代 j 使 n / (j + 1) 以预期方式读取(这个数字是“有替换的”;我认为不过,修正项是低阶的)。
【解决方案2】:

反转数组 A 需要我们找到一个排列 B,它满足所有 i 的要求 A[B[i]] == i

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

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

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

此算法的最坏情况运行时间复杂度为 O(n²),平均运行时间复杂度为 O(n log n),最佳运行时间复杂度为- 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 3 沿着这个循环,我们已经完成了第一步。

  2. 索引 1 处的下一个元素 0 属于同一个循环,我们的检查只用一步就可以告诉我们(因为它是向后的一步) .

  3. 其余元素 12 也是如此。

我们总共执行了 4 + 1 + 1 + 1 次“操作”。这是最好的情况。

【讨论】:

    【解决方案3】:

    在 Python 中实现这个explanation

    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]
    

    【讨论】:

      【解决方案4】:

      如果我们尝试在一个位置存储 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 
      

      【讨论】:

      • 这是一个好主意,但只有在您的数据类型大于所需的情况下才有效。例如。在 C 中反转 256 个 uint8_ts 的数组是行不通的。
      猜你喜欢
      • 1970-01-01
      • 2020-03-25
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      • 2020-12-12
      • 2016-03-11
      • 2019-07-23
      • 1970-01-01
      相关资源
      最近更新 更多