Подтвердить что ты не робот

Как проверить, симметричны ли две перестановки?

Учитывая две перестановки A и B из L разных элементов, L четна, назовем эти перестановки "симметричными" (для отсутствия лучшего термина), если существуют n и m, m > n, например (в нотации на питоне):

 - A[n:m] == B[L-m:L-n]
 - B[n:m] == A[L-m:L-n]
 - all other elements are in place

Неформально рассмотрим

A = 0 1 2 3 4 5 6 7

Возьмите любой его фрагмент, например 1 2. Он начинается со второго индекса, а его длина равна 2. Теперь возьмите симметричный ему срез: он заканчивается в предпоследнем индексе и также длинна 2 символа, поэтому он 5 6. Переключение этих фрагментов дает

B = 0 5 6 3 4 1 2 7

Теперь A и B являются "симметричными" в указанном выше смысле (n=1, m=3). С другой стороны,

A = 0 1 2 3 4 5 6 7
B = 1 0 2 3 4 5 7 6

не являются "симметричными" (нет n,m с указанными выше свойствами).

Как я могу написать алгоритм в python, который обнаруживает, что две заданные перестановки (= списки) являются "симметричными", и если да, найдите n и m? Для простоты рассмотрим только четный L (так как нечетный случай может быть тривиально уменьшен до четного путем исключения среднего фиксированного элемента) и считать правильные входы (set(A)==set(B), len(set(A))==len(A)).

(У меня нет проблем переубедить все возможные симметрии, но искать что-то умнее и быстрее).

Интересный факт: число симметричных перестановок для данного L есть Треугольное число.

Я использую этот код, чтобы проверить ваши ответы.

Обновление Bounty: здесь много отличных ответов. @Решение Jared Goguen является самым быстрым.

Окончательные тайминги:

testing 0123456789 L= 10
    test_alexis ok in 15.4252s
    test_evgeny_kluev_A ok in 30.3875s
    test_evgeny_kluev_B ok in 27.1382s
    test_evgeny_kluev_C ok in 14.8131s
    test_ian ok in 26.8318s
    test_jared_goguen ok in 10.0999s
    test_jason_herbburn ok in 21.3870s
    test_tom_karzes ok in 27.9769s
4b9b3361

Ответ 1

Я переписал код без некоторой сложности (и ошибок).

def test_o_o(a, b):

    L = len(a)
    H = L//2
    n, m = 0, H-1

    # find the first difference in the left-side
    while n < H:
        if a[n] != b[n]: break
        n += 1
    else: return

    # find the last difference in the left-side
    while m > -1:
        if a[m] != b[m]: break 
        m -= 1
    else: return

    # for slicing, we want end_index+1
    m += 1

    # compare each slice for equality
    # order: beginning, block 1, block 2, middle, end
    if (a[0:n] == b[0:n] and \
        a[n:m] == b[L-m:L-n] and \
        b[n:m] == a[L-m:L-n] and \
        a[m:L-m] == b[m:L-m] and \
        a[L-n:L] == b[L-n:L]):

        return n, m

Реализация является элегантной и эффективной.

Структуры break в else: return гарантируют, что функция вернется в максимально возможной точке. Они также подтверждают, что n и m установлены на допустимые значения, но это не представляется необходимым при явной проверке срезов. Эти строки могут быть удалены без заметного влияния на время.

Явное сравнение срезов также будет короткозамкнуто, как только получится False.

Изначально я проверял, существовала ли перестановка путем преобразования b в a:

b = b[:]
b[n:m], b[L-m:L-n] = b[L-m:L-n], b[n:m]
if a == b:
   return n, m

Но это медленнее, чем прямое сравнение срезов. Дайте мне знать, если алгоритм не говорит сам за себя, и я могу предложить дополнительное объяснение (возможно, даже доказательство) относительно того, почему он работает и минимален.

Ответ 2

Вот рабочее решение для вопроса:

def isSymmetric(A, B):
    L = len(A) #assume equivalent to len(B), modifying this would be as simple as checking if len(A) != len(B), return []
    la = L//2 # half-list length
    Al = A[:la]
    Ar = A[la:]
    Bl = B[:la]
    Br = B[la:]
    for i in range(la):
        lai = la - i #just to reduce the number of computation we need to perform
        for j in range(1, lai + 1):
            k = lai - j #same here, reduce computation
            if Al[i] != Br[k] or Ar[k] != Bl[i]: #the key for efficient computation is here: do not proceed unnecessarily
                 continue
            n = i #written only for the sake of clarity. i is n, and we can use i directly
            m = i + j
            if A[n:m] == B[L-m:L-n] and B[n:m] == A[L-m:L-n]: #possibly symmetric
                if A[0:n] == B[0:n] and A[m:L-m] == B[m:L-m] and A[L-n:] == B[L-n:]:
                    return [n, m]
    return []

Как вы уже упоминали, хотя идея выглядит просто, но на самом деле она довольно сложная. Однако, когда мы видим шаблоны, реализация выполняется прямолинейно.

Центральная идея решения - это единственная строка:

if Al[i] != Br[k] or Ar[k] != Bl[i]: #the key for efficient computation is here: do not proceed unnecessarily

Все остальные строки представляют собой либо прямой перевод кода из инструкции задачи, либо оптимизацию, предназначенную для более эффективного вычисления.


Для поиска решения есть несколько шагов:

Во-первых, нам нужно разделить каждый из двух списков A и перечислить B на два полу-списка (называемые Al, Ar, Bl и Br). Каждый полу-список будет содержать половину членов исходных списков:

Al = A[:la]
Ar = A[la:]
Bl = B[:la]
Br = B[la:]

Во-вторых, чтобы сделать оценку эффективной, целью здесь является найти то, что я бы назвал сводным индексом, чтобы решить, будет ли позиция в список (индекс) оценивается или не проверяет, являются ли списки симметричными. Этот опорный индекс является центральной идеей для нахождения эффективного решения. Поэтому я попытался бы его немного доработать:

Рассмотрим левую половину списка A, предположим, что у вас есть такой элемент:

Al = [al1, al2, al3, al4, al5, al6]

Мы можем представить, что для указанного списка есть соответствующий индекс, как этот

Al  = [al1, al2, al3, al4, al5, al6]
iAl = [0,   1,   2,   3,   4,   5  ] #corresponding index list, added for explanation purpose

(Примечание: причина, по которой я упоминаю о представлении соответствующего списка индексов, упрощает объяснение)

Аналогично, мы можем представить, что в остальных трех списках могут быть одинаковые списки индексов. Назовите их iAr, iBl и iBr соответственно, и все они имеют одинаковые элементы с iAl.

Это список индексов списков, которые нам действительно нужны, чтобы решить проблему.


Вот что я имею в виду: предположим, что у нас есть два параметра:

  • index (дайте ему имя переменной i, и я бы использовал символ ^ для текущего i)
  • length (дайте ему имя переменной j, и я бы использовал символ == для визуального представления его значения длины)

для каждой оценки элемента index в iAl - тогда каждая оценка будет означать:

Учитывая значение index i и значение длины j в iAl, выполните что-то, чтобы определить, стоит ли проверять симметричность квалификацию, начиная с этого индекса и с этой длиной(Отсюда следует название индекс pivot).

Теперь давайте возьмем пример одной оценки, когда i = 0 и j = 1. Оценка может быть проиллюстрирована следующим образом:

iAl = [0, 1, 2, 3, 4, 5]
       ^ <-- now evaluate this index (i) = 0
       == <-- now this has length (j) of 1

Для того, чтобы те индекс i и длина j оценивались дополнительно, то аналогичный iBr должен иметь одно и то же значение элемента с то же длина, но на другом индексе (пусть назовите его index k)

iBr = [0, 1, 2, 3, 4, 5]
                      ^ <-- must compare the value in this index to what is pointed by iAl
                      == <-- must evaluate with the same length = 1

Например, для вышеуказанного случая это возможная "симметричная" перестановка только для двух списков Al-Br (мы рассмотрим другие два списка Ar-Bl позже):

Al = [0, x, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, x, 0]

В этот момент хорошо заметить, что

Это не стоит оценивать далее, если даже указанное выше условие не правда

И здесь вы получите алгоритм более эффективный; т.е. выборочно оценивая только несколько возможных случаев среди всех возможных случаев. И как найти несколько возможных случаев?

Попытавшись найти связь между индексами и длинамичетыре списка. То есть для заданного индекса i и длины j в (скажем Al), что должно быть index k в партнере(в случае Br). Длина для списка партнеров не требуется потому что он такой же, как в списке (т.е. j).

Знайте, что теперь давайте посмотрим, можем ли мы увидеть больше шаблонов в процессе оценки.


Рассмотрим теперь эффект длины (j). Например, если мы хотим оценить из index 0, но длина 2, тогда для сопоставления списка должен быть выбран другой индекс k, чем при длине 1

iAl = [0, 1, 2, 3, 4, 5]
       ^ <-- now evaluate this index (i) = 0
       ===== <-- now this has length (j) of 2

iBr = [0, 1, 2, 3, 4, 5]
                   ^ <-- must compare the value in this index to what is pointed by iAl
                   ===== <-- must evaluate with the same length = 2

Или, для иллюстрации выше, то, что действительно имеет значение fox i = 0 и y = 2, выглядит примерно так:

# when i = 0 and y = 2
Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later

Посмотрите, что приведенный выше шаблон немного отличается от i = 0 и y = 1 - позиция индекса для 0 значение в этом примере сдвинута:

# when i = 0 and y = 1, k = 5
Al = [0, x, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, x, 0]

# when i = 0 and y = 2, k = 4
Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later

Таким образом, длина сдвигается, где должен быть проверен индекс . В первом случае, когда i = 0 и y = 1, тогда k = 5. Но во втором случае, когда i = 0 и y = 1, тогда k = 4. Таким образом, мы обнаружили опорные индексы, когда мы изменяем длину j для фиксированного индекса i (в данном случае это 0) к списку-партнеру index k.


Теперь рассмотрим эффекты index i с фиксированной длиной j для сопоставленного списка index k. Например, пусть исправить длину как y = 4, а затем для index i = 0:

iAl = [0, 1, 2, 3, 4, 5]
       ^ <-- now evaluate this index (i) = 0
       ========== <-- now this has length (j) of 4

iAl = [0, 1, 2, 3, 4, 5]
          ^ <-- now evaluate this index (i) = 1
          ========== <-- now this has length (j) of 4

iAl = [0, 1, 2, 3, 4, 5]
             ^ <-- now evaluate this index (i) = 2
             ========== <-- now this has length (j) of 4

#And no more needed

В приведенном выше примере видно, что нам нужно оценить возможности 3 для данных i и j, но если index i изменяется на 1 с той же длиной j = 4:

iAl = [0, 1, 2, 3, 4, 5]
          ^ <-- now evaluate this index (i) = 1
          ========== <-- now this has length (j) of 4

iAl = [0, 1, 2, 3, 4, 5]
             ^ <-- now evaluate this index (i) = 2
             ========== <-- now this has length (j) of 4

Обратите внимание, что нам нужно только оценить возможности 2. Таким образом, увеличение индекса i уменьшает количество возможных случаев, которые необходимо оценить!


Со всеми найденными выше шаблонами мы почти нашли все основания, необходимые для того, чтобы алгоритм работал. Но для этого нам нужно найти связь между индексами, которые появляются в паре Al-Br для данного [i, j] => [k, j] с парой индексов в Ar-Bl для тот же [i, j].

Теперь мы можем видеть, что они просто зеркалируют взаимосвязь, найденную в паре Al-Br!

(ИМХО, это действительно красиво! и, следовательно, я думаю, что термин "симметричная" перестановка не далеко от истины)

Например, если мы имеем следующую пару Al-Br, оцененную с помощью i = 0 и y = 2

Al = [0, y, x, x, x, x] #x means don't care for now
Br = [x, x, x, x, 0, y] #y means to be checked later

Затем, чтобы сделать его симметричным, мы должны иметь соответствующий Ar-Bl:

Ar = [x, x, x, x, 3, y] #x means don't care for now
Bl = [3, y, x, x, x, x] #y means to be checked later

индексирование пары Al-Br зеркалирование (или, симметрично) индексации пары Ar-Bl!


Поэтому, объединив весь шаблон, который мы нашли выше, теперь мы можем найти сводные индексы для оценки Al, Ar, Bl и Br.

Нам нужно только проверить значения списков в сводном индексепервый. Если значения списков в сводных индексах Al, Ar, Bl и Brсоответствует оценке , тогда и только тогда нам нужно проверить для симметричных критериев (что делает расчет эффективным!)


Вводя все вышеперечисленные знания в код, в результате получается код for-loop Python для проверки симметричности:

for i in range(len(Al)): #for every index in the list
    lai = la - i #just simplification
    for j in range(1, lai + 1): #get the length from 1 to la - i + 1
        k = lai - j #get the mirror index
        if Al[i] != Br[k] or Ar[k] != Bl[i]: #if the value in the pivot indexes do not match
             continue #skip, no need to evaluate
        #at this point onwards, then the values in the pivot indexes match
        n = i #assign n
        m = i + j #assign m
        #test if the first two conditions for symmetric are passed
        if A[n:m] == B[L-m:L-n] and B[n:m] == A[L-m:L-n]: #possibly symmetric
            #if it passes, test the third condition for symmetric, the rests of the elements must stay in its place
            if A[0:n] == B[0:n] and A[m:L-m] == B[m:L-m] and A[L-n:] == B[L-n:]:                   
                return [n, m] #if all three conditions are passed, symmetric lists are found! return [n, m] immediately!
        #passing this but not outside of the loop means 
        #any of the 3 conditions to find symmetry are failed
        #though values in the pivot indexes match, simply continue
return [] #nothing can be found - asymmetric lists

И там вы с симметричным тестом!

(ОК, это довольно сложная задача, и мне нужно время, чтобы понять, как это сделать.)

Ответ 3

Я попытался реализовать 3 разных алгоритма для этой задачи. Все они имеют сложность времени O (N) и требуют O (1) дополнительного пространства. Интересный факт: все остальные ответы (известные до сих пор) реализуют 2 из этих алгоритмов (хотя они не всегда сохраняют оптимальную асимптотическую сложность времени/пространства). Вот описание высокого уровня для каждого алгоритма:

Алгоритм A

  • Сравните списки, группы "не равные" интервалы, убедитесь, что есть ровно два таких интервала (со специальным случаем, когда интервалы встречаются посередине).
  • Убедитесь, что "неравные" интервалы расположены симметрично, а их содержимое также "симметрично".

Алгоритм B

  • Сравните первые половины списков, чтобы угадать, где находятся "интервалы, которые нужно обменять".
  • Проверьте, является ли содержимое этих интервалов "симметричным". И убедитесь, что списки равны за пределами этих интервалов.

Алгоритм C

  • Сравните первые половины списков, чтобы найти первый несогласованный элемент.
  • Найдите этот несогласованный элемент первого списка во втором. Это указывает на положение "интервалов, которые нужно обменять".
  • Проверьте, является ли содержимое этих интервалов "симметричным". И убедитесь, что списки равны за пределами этих интервалов.

Существует два альтернативных варианта для шага 1 каждого алгоритма: (1) использование itertools и (2) использование простых циклов (или списков). itertools эффективны для длинных списков, но относительно короткие в коротких списках.

Вот алгоритм C с первым шагом, реализованным с использованием itertools. Это выглядит проще, чем другие два алгоритма (в конце этого сообщения). И это довольно быстро, даже для коротких списков:

import itertools as it
import operator as op

def test_C(a, b):
    length = len(a)
    half = length // 2
    mismatches = it.imap(op.ne, a, b[:half]) # compare half-lists

    try:
        n = next(it.compress(it.count(), mismatches))
        nr = length - n
        mr = a.index(b[n], half, nr)
        m = length - mr
    except StopIteration: return None
    except ValueError: return None

    if a[n:m] == b[mr:nr] and b[n:m] == a[mr:nr] \
            and a[m:mr] == b[m:mr] and a[nr:] == b[nr:]:
        return (n, m)

Это можно сделать, используя в основном itertools:

def test_A(a, b):
    equals = it.imap(op.eq, a, b) # compare lists
    e1, e2 = it.tee(equals)
    l = it.chain(e1, [True])
    r = it.chain([True], e2)
    borders = it.imap(op.ne, l, r) # delimit equal/non-equal intervals
    ranges = list(it.islice(it.compress(it.count(), borders), 5))

    if len(ranges) == 4:
        n1, m1 = ranges[0], ranges[1]
        n2, m2 = ranges[2], ranges[3]
    elif len(ranges) == 2:
        n1, m1 = ranges[0], len(a) // 2
        n2, m2 = len(a) // 2, ranges[1]
    else:
        return None

    if n1 == len(a) - m2 and m1 == len(a) - n2 \
            and a[n1:m1] == b[n2:m2] and b[n1:m1] == a[n2:m2]:
        return (n1, m1)

Высокоуровневое описание этого алгоритма уже представлено в комментариях OP by @j_random_hacker. Вот несколько деталей:

Начните с сравнения списков:

A  0 1 2 3 4 5 6 7
B  0 5 6 3 4 1 2 7
=  E N N E E N N E

Затем найдите границы между равными/не равными интервалами:

=  E N N E E N N E
B  _ * _ * _ * _ *

Затем определите диапазоны для неравных элементов:

B  _ * _ * _ * _ *
    [1 : 3] [5 : 7]

Затем проверьте, есть ли ровно 2 диапазона (со специальным случаем, когда оба диапазона встречаются посередине), сами диапазоны симметричны и их содержимое тоже.


Другой альтернативой является использование itertools для обработки только половины каждого списка. Это позволяет немного упростить (и немного быстрее) алгоритм, потому что нет необходимости обрабатывать специальный случай:

def test_B(a, b):
    equals = it.imap(op.eq, a, b[:len(a) // 2]) # compare half-lists
    e1, e2 = it.tee(equals)
    l = it.chain(e1, [True])
    r = it.chain([True], e2)
    borders = it.imap(op.ne, l, r) # delimit equal/non-equal intervals
    ranges = list(it.islice(it.compress(it.count(), borders), 2))

    if len(ranges) != 2:
        return None

    n, m = ranges[0], ranges[1]
    nr, mr = len(a) - n, len(a) - m

    if a[n:m] == b[mr:nr] and b[n:m] == a[mr:nr] \
            and a[m:mr] == b[m:mr] and a[nr:] == b[nr:]:
        return (n, m)

Ответ 4

Я строю карту того, где символы находятся в списке B, а затем используйте это, чтобы определить подразумеваемые поддиапазоны в списке A. После того, как у меня есть поддиапазоны, я могу проверить работоспособность некоторой информации и сравнить фрагменты.

Если A[i] == x, то где x появляется в B? Вызовите эту позицию p.

Я знаю i, начало левого поддиапазона.

Я знаю L (= len(A)), поэтому я знаю L-i, конец правого поддиапазона.

Если я знаю p, то я знаю подразумеваемый запуск правого поддиапазона, предполагая, что B [p] и A [i] являются началом симметричной пары диапазонов. Таким образом, OP L - m будет p, если списки были симметричными.

Настройка L-m == p дает мне m, поэтому у меня есть все четыре конечные точки.

Тесты на герметичность:

  • n и m находятся в левой половине списка (-ов)
  • n <= m (примечание: OP не запрещает n == m)
  • L-n находится в правой половине списка (вычисляется)
  • L-m находится в правой половине (это хорошая проверка для быстрого сбоя)

Если все эти проверки, сравните A [left] == ​​B [right] и B [left] == ​​A [right]. Возвращаем left, если true.

def find_symmetry(a:list, b:list) -> slice or None:

    assert len(a) == len(b)
    assert set(a) == set(b)
    assert len(set(a)) == len(a)

    length = len(a)
    assert length % 2 == 0

    half = length // 2
    b_loc = {bi:n for n,bi in enumerate(b)}

    for n,ai in enumerate(a[:half]):
        L_n = length - 1 - n    # L - n
        L_m = b_loc[ai]         # L - m (speculative)

        if L_m < half:         # Sanity: bail if on wrong side
            continue

        m = b_loc[a[L_n]]  # If A[n] starts range, A[m] ends it.

        if m < n or m > half:   # Sanity: bail if backwards or wrong side
            continue

        left = slice(n, m+1)
        right = slice(L_m, L_n+1)

        if a[left] == b[right] and \
            b[left] == a[right]:
            return left

    return None

res = find_symmetry(
        [ 10, 11, 12, 13, 14, 15, 16, 17, ],
        [ 10, 15, 16, 13, 14, 11, 12, 17, ])
assert res == slice(1,3)

res = find_symmetry(
    [ 0, 1, 2, 3, 4, 5, 6, 7, ],
    [ 1, 0, 2, 3, 4, 5, 7, 6, ])
assert res is None

res = find_symmetry("abcdefghijklmn", "nbcdefghijklma")
assert res == slice(0,1)

res = find_symmetry("abcdefghijklmn", "abjklfghicdmen")
assert res == slice(3,4)

res = find_symmetry("abcdefghijklmn", "ancjkfghidelmb")
assert res == slice(3,5)

res = find_symmetry("abcdefghijklmn", "bcdefgaijklmnh")
assert res is None

res = find_symmetry("012345", "013245")
assert res == slice(2,3)

Ответ 5

Это правильно:

Br = B[L//2:]+B[:L//2]
same_full = [a==b for (a,b) in zip(A, Br)]
same_part = [a+b for (a,b) in zip(same_full[L//2:], same_full[:L//2])]

for n, vn in enumerate(same_part):
    if vn != 2:
        continue
    m = n
    for vm in same_part[n+1:]:
        if vm != 2:
            break
        m+=1

    if m>n:
        print("n=", n, "m=", m+1)

Я уверен, что вы можете сделать счетчик бит, но... meh

Ответ 6

Я считаю, что следующий псевдокод должен работать:

  • Найдите первый элемент i, для которого A[i] != B[i], установите n = i. Если такого элемента нет, верните success. Если n >= L/2, верните fail.
  • Найдите первый элемент i > n, для которого A[i] == B[i], установите m = i. Если нет такого элемента или m > L/2, установите m = L/2.
  • Проверьте, что A[0:n] == B[0:n], A[n:m] == B[L-m:L-n], B[n:m] == A[L-m:L-n], A[m:L-m] == B[m:L-m] и A[L-n:L] == B[L-n:L]. Если все верно, верните success. Else, верните fail.

Сложность - это O (n), которая должна быть минимально возможной, поскольку всегда нужно сравнивать все элементы в списках.

Ответ 7

Здесь простое решение, которое проходит мои тесты, и ваше:

  • Сравните входы, ища подпоследовательность, которая не соответствует.
  • Преобразование A путем переноса несогласованной подпоследовательности в соответствии с правилами. Соответствует ли результат B?

Алгоритм O (N); нет встроенных циклов, явных или неявных.

На шаге 1 мне нужно определить случай, когда подстановочные подстановки смежны. Это может произойти только в середине строки, но мне было проще просто искать первый элемент перемещенной части (firstval). Шаг 2 упрощен (и, следовательно, меньше подвержен ошибкам), чем явно проверяет все ограничения.

def compare(A, B):
    same = True
    for i, (a, b) in enumerate(zip(A,B)):
        if same and a != b:  # Found the start of a presumed transposition
            same = False
            n = i
            firstval = a  # First element of the transposed piece
        elif (not same) and (a == b or b == firstval):  # end of the transposition
            m = i
            break

    # Construct the transposed string, compare it to B
    origin = A[n:m] 
    if n == 0:  # swap begins at the edge
        dest = A[-m:]
        B_expect = dest + A[m:-m] + origin
    else:
        dest = A[-m:-n]
        B_expect = A[:n] + dest + A[m:-m] + origin + A[-n:]

    return bool(B_expect == B)

Использование примера:

>>> compare("01234567", "45670123")
True

Бонус: Я считаю, что имя для этой связи было бы "симметричной транспозицией блока". A Транспонирование блока свопирует две подпоследовательности, принимая ABCDE до ADCBE. (См. Определение 4 здесь; на самом деле я нашел это в googling "ADCBE" ). Я добавил "симметричный" к имени, чтобы описать условия длины.

Ответ 8

Составьте список (ds) индексов, где первые половины этих двух списков отличаются. Возможным n является первый такой индекс, последний такой индекс - m - 1. Проверьте правильность симметрии. len (ds) == m - n гарантирует отсутствие пробелов.

import itertools as it
import operator as op

def test(a, b):
    sz = len(a)
    ds = list(it.compress(it.count(), map(op.ne, a[:sz//2], b[:sz//2])))
    n,m = ds[0], ds[-1]+1
    if a[n:m] == b[sz-m:sz-n] and b[n:m] == a[sz-m:sz-n] and len(ds) == m - n:
        return n,m
    else:
        return None

Ответ 9

Здесь O (N) решение, которое передает тестовый код:

def sym_check(a, b):
    cnt = len(a)

    ml = [a[i] == b[i] for i in range(cnt)]

    sl = [i for i in range(cnt) if (i == 0 or ml[i-1]) and not ml[i]]
    el = [i+1 for i in range(cnt) if not ml[i] and (i == cnt-1 or ml[i+1])]

    assert(len(sl) == len(el))

    range_cnt = len(sl)

    if range_cnt == 1:
        start1 = sl[0]
        end2 = el[0]

        if (end2 - start1) % 2 != 0:
            return None

        end1 = (start1 + end2) // 2
        start2 = end1

    elif range_cnt == 2:

        start1, start2 = sl
        end1, end2 = el

    else:
        return None

    if end1 - start1 != end2 - start2:
        return None

    if start1 != cnt - end2:
        return None

    if a[start1:end1] != b[start2:end2]:
        return None

    if b[start1:end1] != a[start2:end2]:
        return None

    return start1, end1

Я тестировал его только с Python 2, но я считаю, что он также будет работать с Python 3.

Он определяет диапазоны, в которых эти два списка отличаются. Он ищет два таких диапазона (если есть только один такой диапазон, он пытается разделить его пополам). Затем он проверяет, что оба диапазона имеют одинаковую длину и в соответствующих положениях относительно друг друга. Если это так, то он проверяет соответствие элементов в диапазонах.

Ответ 10

Еще одна версия:

def compare(a, b):
    i_zip = list(enumerate(zip(a, b)))
    llen = len(a)
    hp = llen // 2

    def find_index(i_zip):
        for i, (x, y) in i_zip:
            if x != y:
                return i
        return i_zip[0][0]

    # n and m are determined by the unmoved items:
    n = find_index(i_zip[:hp])
    p = find_index(i_zip[hp:])
    m = llen - p
    q = llen - n
    # Symmetric?
    if a[:n] + a[p:q] + a[m:p] + a[n:m] + a[q:] != b:
        return None
    return n, m

Это решение основано на:

  • Все допустимые пары пар A, B, соблюдающие требование симметрии, будут иметь структуру:

    A = P1 + P2 + P3 + P4 + P5 B = P1 + P4 + P3 + P2 + P5 ^n ^m ^hp ^p ^q <- indexes

    len (P1) == len (P5) и len (P2) == len (P4)

  • Следовательно, 3 последних строки указанной функции будут определять правильное решение, если будут правильно определены индексы n, m. (p и q являются только зеркальными индексами m и n)
  • Поиск n - это вопрос определения того, когда элементы A и B начинают расходиться. Следующий же метод применяется для нахождения p, начиная с середины hp. m является только зеркальным индексом p. Все задействованные индексы найдены, и решение появляется.