例如,输入为
Array 1 = [2, 3, 4, 5]
Array 2 = [3, 2, 5, 4]
最少需要2
次交换。
交换的元素不一定要相邻,任意两个元素都可以交换。
例如,输入为
Array 1 = [2, 3, 4, 5]
Array 2 = [3, 2, 5, 4]
最少需要2
次交换。
交换的元素不一定要相邻,任意两个元素都可以交换。
正如 @IVlad 在你的问题的评论中指出的那样,Yodaness 问题 要求你计算逆序对数而不是最少交换次数。
例如:
L1 = [2,3,4,5]
L2 = [2,5,4,3]
最少的交换次数是 一次 (将 L2
中的 5 和 3 交换以得到 L1
),但逆序对的数量为 三个:(5 4), (5 3) 和 (4 3) 这几对在错误的顺序中。
计算逆序对的最简单方法遵循定义:
在排列 p 中,如果 i < j 且 pi > pj,则元素对 (pi,pj) 被称为一个逆序对。
Python 代码示例:
def count_inversions_brute_force(permutation):
"""Count number of inversions in the permutation in O(N**2)."""
return sum(pi > permutation[j]
for i, pi in enumerate(permutation)
for j in xrange(i+1, len(permutation)))
使用分治策略(类似于归并排序算法的工作原理),您可以在O(N * log(N))的时间复杂度内计算逆序对。以下是来自计数逆序对的伪代码,已翻译为Python代码:
def merge_and_count(a, b):
assert a == sorted(a) and b == sorted(b)
c = []
count = 0
i, j = 0, 0
while i < len(a) and j < len(b):
c.append(min(b[j], a[i]))
if b[j] < a[i]:
count += len(a) - i # number of elements remaining in `a`
j+=1
else:
i+=1
# now we reached the end of one the lists
c += a[i:] + b[j:] # append the remainder of the list to C
return count, c
def sort_and_count(L):
if len(L) == 1: return 0, L
n = len(L) // 2
a, b = L[:n], L[n:]
ra, a = sort_and_count(a)
rb, b = sort_and_count(b)
r, L = merge_and_count(a, b)
return ra+rb+r, L
示例:
>>> sort_and_count([5, 4, 2, 3])
(5, [2, 3, 4, 5])
以下是针对来自该问题的Python解决方案:
yoda_words = "in the force strong you are".split()
normal_words = "you are strong in the force".split()
perm = get_permutation(normal_words, yoda_words)
print "number of inversions:", sort_and_count(perm)[0]
print "number of swaps:", number_of_swaps(perm)
number of inversions: 11
number of swaps: 5
get_permutation()
和number_of_swaps()
的定义如下:
def get_permutation(L1, L2):
"""Find permutation that converts L1 into L2.
See http://en.wikipedia.org/wiki/Cycle_representation#Notation
"""
if sorted(L1) != sorted(L2):
raise ValueError("L2 must be permutation of L1 (%s, %s)" % (L1,L2))
permutation = map(dict((v, i) for i, v in enumerate(L1)).get, L2)
assert [L1[p] for p in permutation] == L2
return permutation
def number_of_swaps(permutation):
"""Find number of swaps required to convert the permutation into
identity one.
"""
# decompose the permutation into disjoint cycles
nswaps = 0
seen = set()
for i in xrange(len(permutation)):
if i not in seen:
j = i # begin new cycle that starts with `i`
while permutation[j] != i: # (i σ(i) σ(σ(i)) ...)
j = permutation[j]
seen.add(j)
nswaps += 1
return nswaps
get_permutation
,例如使用((v,L1.count(v)), i)
(或其更有效的等效形式)而不是(v,i)
。 - jfskamal -> akmal -> almak -> amalk
。 - Bob Templv
出现的次数”而不是L1.count
(即 list(<the dict>).count(v)
)否则会有重复值(将assert sorted(set(permutation)) == range(len(permutation))
添加到后置条件中)。虽然正如您的示例所示,这还不够。我认为重复项的扩展值得单独提问。 - jfsalmak -> amalk
移动了 3
个字母,而不是 2
,即如果 get_permutation()
被适应用于带有重复的序列,则 number_of_swaps()
在这种情况下也可以工作。我已经为带有重复的情况调整了 get_permutation()
函数(它基于 与相关问题的 @IVlad 的答案中的算法)。 - jfs正如Sebastian的解决方案所暗示的那样,你要寻找的算法可以基于检查置换的循环。
我们应该将数组#2视为对数组#1的置换变换。在你的例子中,置换可以表示为P=[2,1,4,3]。
每个置换都可以表示为一组不相交的循环,代表物品的循环位置变化。例如,置换P有两个循环:(2,1)和(4,3)。因此,只需要进行两次交换。在一般情况下,你只需从置换长度中减去循环数,就可以得到所需最小交换次数。这是因为观察到为了“修复”包含N个元素的循环,只需要进行N-1次交换。
算法:
代码:
def nswaps(l1, l2):
cnt = 0
for i in range(len(l1)):
if l1[i] != l2[i]:
ind = l2.index(l1[i])
l2[i], l2[ind] = l2[ind], l2[i]
cnt += 1
pass
return cnt
这个问题可以很容易地转换成另一种类型的问题,从而更有效地解决。所需的只是将数组转换为排列,即将值更改为它们的ID。因此,您的数组:
L1 = [2,3,4,5]
L2 = [2,5,4,3]
会变成
P1 = [0,1,2,3]
P2 = [0,3,2,1]
有一个分配任务的代码为 2->0, 3->1, 4->2, 5->3
。如果没有重复项,那么这个问题很容易解决。但是如果有重复项,则问题会变得更加困难。
将排列从一个转换到另一个可以转化为类似问题(排列中的交换次数),通过在O(n)中反转目标排列,用O(n)组合排列,然后在O(m)中找到到身份排列的交换次数。 给定:
int P1[] = {0, 1, 2, 3}; // 2345
int P2[] = {0, 3, 2, 1}; // 2543
// we can follow a simple algebraic modification
// (see http://en.wikipedia.org/wiki/Permutation#Product_and_inverse):
// P1 * P = P2 | premultiply P1^-1 *
// P1^-1 * P1 * P = P1^-1 * P2
// I * P = P1^-1 * P2
// P = P1^-1 * P2
// where P is a permutation that makes P1 into P2.
// also, the number of steps from P to identity equals
// the number of steps from P1 to P2.
int P1_inv[4];
for(int i = 0; i < 4; ++ i)
P1_inv[P1[i]] = i;
// invert the first permutation in O(n)
int P[4];
for(int i = 0; i < 4; ++ i)
P[i] = P2[P1_inv[i]];
// chain the permutations in O(n)
int num_steps = NumSteps(P, 4); // will return 2
// now we just need to count the steps in O(num_steps)
为了计算步数,可以设计一个简单的算法,例如:
int NumSteps(int *P, int n)
{
int count = 0;
for(int i = 0; i < n; ++ i) {
for(; P[i] != i; ++ count) // could be permuted multiple times
swap(P[P[i]], P[i]); // look where the number at hand should be
}
// count number of permutations
return count;
}
这个算法总是将一个项目交换到它在恒等置换中应该存在的位置,因此在每一步中它都会撤销并计数一个交换。现在,只要它返回的交换次数确实是最小的,算法的运行时间就受到它的限制,并且保证能够完成(而不是陷入无限循环)。它将在O(m)
次交换或O(m + n)
次循环迭代中运行,其中m
是交换次数(返回的count
),n
是序列中项目的数量(4
)。请注意,m < n
始终为真。因此,这应该优于O(n log n)
的解决方案,因为上限是O(n - 1)
次交换或O(n + n - 1)
次循环迭代,这两种情况下都是实际的O(n)
(后一种情况中省略了2的常数因子)。
该算法仅适用于有效的排列,对于具有重复值的序列,它将无限循环,并且对于值不是[0,n)
的序列进行越界数组访问(并崩溃)。完整的测试用例可以在这里找到(使用Visual Studio 2008构建,算法本身应该相当可移植)。它生成长度为1到32的所有可能的排列,并针对使用广度优先搜索(BFS)生成的解进行检查,似乎适用于长度为1到12的所有排列,然后变得相当缓慢,但我认为它将继续工作。
由于我们已经知道arr2具有arr1中每个元素的正确索引。因此,我们可以简单地将arr1元素与arr2进行比较,并在它们位于错误索引时使用正确的索引进行交换。
def minimum_swaps(arr1, arr2):
swaps = 0
for i in range(len(arr1)):
if arr1[i] != arr2[i]:
swaps += 1
element = arr1[i]
index = arr1.index(arr2[i]) # find index of correct element
arr1[index] = element # swap
arr1[i] = arr2[i]
return swaps
@J.F. Sebastian和@Eyal Schneider的答案非常棒。
我受到启发,解决了一个类似的问题:计算排序数组所需的最小交换次数,例如:要对{2,1,3,0}
进行排序,需要最少2次交换。
这是Java代码:
// 0 1 2 3
// 3 2 1 0 (0,3) (1,2)
public static int sortWithSwap(int [] a) {
Integer[] A = new Integer[a.length];
for(int i=0; i<a.length; i++) A[i] = a[i];
Integer[] B = Arrays.copyOf(mapping(A), A.length, Integer[].class);
int cycles = 0;
HashSet<Integer> set = new HashSet<>();
boolean newCycle = true;
for(int i=0; i<B.length; ) {
if(!set.contains(B[i])) {
if(newCycle) {
newCycle = false;
cycles++;
}
set.add(B[i]);
i = B[i];
}
else if(set.contains(B[i])) { // duplicate in existing cycles
newCycle = true;
i++;
}
}
// suppose sequence has n cycles, each cycle needs swap len(cycle)-1 times
// and sum of length of all cycles is length of sequence, so
// swap = sequence length - cycles
return a.length - cycles;
}
// a b b c
// c a b b
// 3 0 1 1
private static Object[] mapping(Object[] A) {
Object[] B = new Object[A.length];
Object[] ret = new Object[A.length];
System.arraycopy(A, 0, B, 0, A.length);
Arrays.sort(A);
HashMap<Object, Integer> map = new HashMap<>();
for(int i=0; i<A.length; i++) {
map.put(A[i], i);
}
for(int i=0; i<B.length; i++) {
ret[i] = map.get(B[i]);
}
return ret;
}
这似乎是一个编辑距离问题,只不过允许转位。
看看Damerau-Levenshtein距离的伪代码。我相信你可以调整它来仅计算转位。