【问题标题】:Minimum number of swaps needed to change Array 1 to Array 2?将阵列 1 更改为阵列 2 所需的最少交换次数?
【发布时间】:2011-02-28 13:53:13
【问题描述】:

例如输入是

Array 1 = [2, 3, 4, 5]
Array 2 = [3, 2, 5, 4]

所需的最少交换次数为2

交换不必与相邻单元格,任何两个元素都可以交换。

https://www.spoj.com/problems/YODANESS/

【问题讨论】:

  • 我正在尝试spoj.pl/problems/YODANESS
  • 在我看来,这个问题要求你计算反转,而不是你在这里问的问题。
  • 寻求解决 SPOJ 问题的方法是否可行?你不应该试着自己弄清楚吗?你为什么不要求提示而不是答案?
  • 我认为这很大程度上取决于列表中是否允许重复。如果不是这样的话,问题就很简单了,但如果是这样的话,问题就很棘手了。

标签: algorithm


【解决方案1】:

正如@IVlad 在对您的问题Yodaness problem 的评论中指出的那样,要求您计算number of inversions 而不是最少的交换次数。

例如:

L1 = [2,3,4,5]
L2 = [2,5,4,3]

最小交换次数为 1(在 L2 中交换 5 和 3 以得到 L1),但反转次数为 3:(5 4 )、(5 3) 和 (4 3) 对的顺序错误。

计算反转次数的最简单方法来自the definition

如果 i i,则一对元素 (pi,pj) 称为置换 p 中的反转> 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)) 中的反转(类似于a merge sort algorithm 的工作方式)。下面是从Counting Inversions 翻译成 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])

以下是the problem 示例的 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)
  • 这似乎会导致同样的事情。例如,对两个输入 'kamal''amalk' 使用任何一种方法都会生成一个看起来像 [3,2,3,4,0] 的排列;理想情况下是[1,2,3,4,0]。我想我还应该注意,即使手动传递排列[1,2,3,4,0],当最小值为 3 时,我也会得到 4 次交换; kamal -&gt; akmal -&gt; almak -&gt; amalk.
  • 我的意思是“到目前为止看到v 的次数”而不是L1.count(即list(&lt;the dict&gt;).count(v))否则会有重复(在后置条件中添加assert sorted(set(permutation)) == range(len(permutation))) .尽管正如您的示例所示,但这还不够。我认为重复的扩展值得自己的问题。
  • @BobTempl: almak -&gt; amalk 移动 3 字母,而不是 2 即,如果 get_permutation() 适用于重复序列,则 number_of_swaps() 在这种情况下也适用。我改编了get_permutation() function for case with duplicates(它基于the algorithm from @IVlad's answer to the related question
【解决方案2】:

正如 Sebastian 的解决方案所暗示的,您正在寻找的算法可以基于检查 permutation's cycles

我们应该将数组#2 视为数组#1 的置换变换。在您的示例中,排列可以表示为 P = [2,1,4,3]。

每个排列都可以表示为一组不相交的循环,表示项目的循环位置变化。例如,置换 P 有 2 个循环:(2,1) 和 (4,3)。因此,两次交换就足够了。在一般情况下,您应该简单地从排列长度中减去循环数,并且您可以获得所需的最小交换次数。这是从以下观察得出的:为了“修复”一个包含 N 个元素的循环,N-1 次交换就足够了。

【讨论】:

    【解决方案3】:

    这个问题有一个干净、贪婪、琐碎的解决方案:

    1. 查找任何使 both 交换的 Array1 中的元素更接近它们在 Array2 中的目的地的交换操作。如果存在,则对 Array1 执行交换操作。
    2. 重复第 1 步,直到不再存在此类交换操作。
    3. 查找任何使 Array1 中的 一个 交换元素更接近其在 Array2 中的目标的交换操作。如果存在这样的操作,请在 Array1 上执行。
    4. 返回步骤 1,直到 Array1 == Array2。

    算法的正确性可以通过将问题的潜在性定义为array1中所有元素到array2中它们的目的地的距离之和来证明。

    【讨论】:

      【解决方案4】:

      这可以很容易地转换为另一种类型的问题,可以更有效地解决。所需要做的就是将数组转换为排列,即将值更改为它们的 id。所以你的数组:

      L1 = [2,3,4,5]
      L2 = [2,5,4,3]
      

      会变成

      P1 = [0,1,2,3]
      P2 = [0,3,2,1]
      

      分配2-&gt;0, 3-&gt;1, 4-&gt;2, 5-&gt;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 &lt; n 始终为真。因此,这应该优于O(n log n) 解决方案,因为上限是交换的O(n - 1) 或循环迭代的O(n + n - 1),实际上两者都是O(n)(在后一种情况下省略了常数因子2)。

      该算法仅适用于有效排列,它将无限循环用于具有重复值的序列,并对具有[0, n) 以外的值的序列进行越界数组访问(并崩溃)。可以在here 找到完整的测试用例(使用 Visual Studio 2008 构建,算法本身应该是相当可移植的)。它生成长度为 1 到 32 的所有可能排列,并检查使用广度优先搜索 (BFS) 生成的解决方案,似乎适用于长度为 1 到 12 的所有排列,然后它变得相当慢,但我认为它会继续工作.

      【讨论】:

      • 没有必要将这个问题转化为另一个问题,然后需要与原始问题相同的努力,转化为不同的问题需要您解决一个更简单的问题,但这不是你的答案的情况。找到周期和交换次数是这里最好的方法。
      • @MithunS 请注意,当前接受的答案是如何从形成排列开始的,然后执行类似的算法。除了显示问题与其他问题的关系外,此答案还附带代码、测试和证明。感谢您的反对。
      • 否决票是因为您对索引的转换是不必要的。另外,您不需要在数组上运行 3 个循环来执行此操作。您能否解释一下您的转型如何使解决方案变得高效?您确实知道,要找出所需的最小交换次数,您实际上不必交换元素,您只需要找到循环就可以了。
      【解决方案5】:

      算法:

      1. 检查列表中相同位置的元素是否相等。如果是,则不需要交换。如果否,则在元素匹配的任何位置交换列表元素的位置
      2. 迭代整个列表元素的过程。

      代码:

      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
      

      【讨论】:

      • 解释你的代码和你的算法。与其他响应相比,这一代码转储质量非常低。你带来了什么新东西吗?
      【解决方案6】:

      因为我们已经知道 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
      

      【讨论】:

      • 虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请edit您的回答添加解释并说明适用的限制和假设。
      • @double-beep 我添加了描述。
      【解决方案7】:

      @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;
      }
      

      【讨论】:

        【解决方案8】:

        这似乎是一个edit distance 问题,只是只允许换位。

        查看Damerau–Levenshtein distance 伪代码。我相信您可以将其调整为仅计算换位。

        【讨论】:

        • 我并没有真正获得有关 Damerau-Levenshtein 算法的维基百科文章。据我所知,它说提供的实现并没有真正起作用:In reality this algorithm calculates the cost of the so-called optimal string alignment, which does not always equal the edit distance. It is also easy to see that the cost of the optimal string alignment is the number of edit operations needed to make the strings equal under the condition that no substring is edited more than once
        猜你喜欢
        • 1970-01-01
        • 2013-05-06
        • 1970-01-01
        • 2020-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多