Минимальное количество свопов для преобразования строки в другую строку

Проблема:

Найдите минимальное количество свопов, чтобы преобразовать одну строку в другую той же длины, которая может иметь или не иметь повторяющихся символов; допускаются произвольные свопы.

min_swaps('kamal', 'amalk') -> 3
#       1         2        3
# kamal -> lamak -> aamlk -> amalk

Примечание. На SO есть много таких вопросов, но ни один из них не применяется к произвольным свопам.

Начальный подход:

let s1 = 'kamal'
let s2 = 'amalk'

Предположим, что s1 является "правильным" упорядочением, то есть его элементы отображают последовательность из 0 -> N-1 в порядке возрастания.

0 1 2 3 4
k a m a l

Теперь создайте массив P, который является отображением из букв в s2 в правильный индекс в s1:

1 2 3 4 0
a m a l k 

P = [1,2,3,4,0]

Теперь мы можем подсчитать количество инверсий массива в P с помощью модифицированной слияния, которая даст нам количество элементов, которые не соответствуют порядку.

Модифицированный mergesort:

int main(int argc, char ** argv) {
   int array[] = { 1,2,3,4,0 };

   int array_size = sizeof(array)/sizeof(array[0]);
   int inversions = merge_sort(array, 0, array_size - 1);

   printf("Found %d inversions\n", inversions);
   return 0;
}

int merge_sort(int a[], int start, int end) {
   if ( end > start ) {
      int mid = start + (end - start) / 2;
      int x = merge_sort(a, start, mid);
      int y = merge_sort(a, mid + 1, end);
      int z = merge(a, start, mid, end);

      return x + y + z;
   }

   return 0;
}

int merge(int a[], int start, int mid, int end) {
   int l = start, r = mid + 1;
   int i = 0;
   int temp[end - start + 1];
   int splitInversionCount = 0;

   while ( l <= mid && r <= end ) {
       if ( a[l] < a[r] ) {
          temp[i++] = a[l++];
       }
       else {
         splitInversionCount += mid - l + 1;
         temp[i++] = a[r++];
       }
   }

   // copy whichever half of the array remains
   while ( l <= mid ) {
      temp[i++] = a[l++];
   }

   while ( r <= end ) {
      temp[i++] = a[r++];
   }

   // copy temp back into a
   int k;
   for(k = 0; k < i; k++) {
      a[k + start] = temp[k];
   }

   return splitInversionCount;
}

Проблема заключается в том, что он дает минимальное количество свопов только с соседними элементами, это возвращает 4 вместо 3.

Вопрос:

Есть ли способ расширить этот алгоритм для захвата произвольных свопов? Или мне нужен совершенно другой подход?

Ответ 1

У меня пока нет полного решения, но я считаю полезным указать это, чтобы помочь другим.

Предположим, что строки имеют размер N. Позвоните k количество символов, которые уже находятся в их положении. Первоначально k <= N

Подкачка может иметь:

  • Установите 2 символа в правильном положении. Увеличить k на 2
  • Установите 1 символ в правильном положении. Увеличить k на 1
  • Ничего полезного, k остается тем же.

Вы всегда можете сделать переход второго рода, и вы можете найти его в O (n) времени. Просто возьмите один символ из позиции и найдите позицию, в которой он должен быть, затем поменяйте.

Вы не должны делать своп первого типа, как только увидите его, так как вы можете сломать слишком другие ходы, которые одновременно устанавливают 2 символа. Если вы это сделаете, вы в конечном итоге установите 4 символа в 3 ходах, если вы могли бы сделать это в 2.

Создание движения третьего рода может позволить сделать 2 хода первого рода, поэтому все три вида движений полезны.