假设我在 [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]
答案 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
中的即将到来的元素,我们的计算将会失败。
因此,我们必须遵循A
以c = 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]
:
答案 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