Як перевірити, чи два рядки перестановки один одного, використовуючи додатковий простір O (1)?


13

Дано два рядки, як ви можете перевірити, чи є вони перестановкою один одного, використовуючи пробіл O (1)? Змінювати рядки не дозволяється жодним чином.
Примітка: пробіл O (1) стосовно довжини рядка ТА розміру алфавіту.


3
Що ти думаєш? Що ви пробували, і де ви застрягли? Чи є рядки над алфавітом постійного розміру? Ви спробували обчислити їх гістограми?
Yuval Filmus

@YuvalFilmus це має бути пробіл O (1) як по довжині рядка, так і за розміром алфавіту
Anonymous

Це здається явно неможливим. Будь-який алгоритм потребує додаткового простору для зберігання принаймні позиції в одній рядку або одного символу. Жодне з цих речей не є O (1).
Девід Шварц

@DavidSchwartz - як? O (1) означає постійну, а не одну бату. Не має значення, як довга струна, розташуйте в ній одне число.
Давор

Це залежить від моделі машини, очевидно, що немає проблем у єдиних моделях. У логарифмічній моделі витрат зберігання індексу призначено O(log n)для рядків довжиною n, які не є постійними ні за довжиною, ні за розміром алфавіту. Коли рядки можуть бути тимчасово змінені, я думаю, що існує рішення зі збільшеним алфавітом, який є лінійним за розміром алфавіту, але постійним у довжині рядка в логарифмічній моделі.
кап

Відповіді:


7

Наївний підхід складав би гістограми обох рядків і перевіряв, чи однакові вони. Оскільки нам заборонено зберігати таку структуру даних (розмір якої буде лінійною до розміру алфавіту), яку можна було б обчислити за один прохід, нам потрібно порахувати виникнення кожного можливого символу після іншого:

function count(letter, string)
    var count := 0
    foreach element in string
        if letter = element
            count++
    return count

function samePermutation(stringA, stringB)
    foreach s in alphabet
        if count(s, stringA) != count(s, stringB)
            return false
    return true

Звичайно, це передбачає, що показники підрахунку та ітератора - це цілі числа постійної величини, а не залежать від довжини рядків.


В якості оптимізації ви можете перейти через один масив і обчислити лише гістограми букв, які ви зустрічаєте. Таким чином складність стає незалежною від розміру алфавіту.
Yuval Filmus

Щоб розширити коментар @YuvalFilmus, вам також потрібно 1) перевірити, чи довжини рядків однакові, або 2) повторити два обох вхідних рядка. Вам потрібна одна з них, оскільки можливо, деякі букви в одній не в іншій. Варіант 1 повинен мати менше обчислень.
BurnsBA

@YuvalFilmus Я хотів уникнути цього, оскільки це означатиме квадратичну складність часу, я б очікував, що алфавіт буде меншим, ніж середній розмір рядка. Для невеликих рядків та впорядкованого алфавіту я б розглядав можливість обчислення наступного найменшого символу, що присутній разом із підрахунком у внутрішньому циклі, щоб можна було пропустити кілька ітерацій циклу алфавіту - зі складністю O(n * min(n, |Σ|)). Гм, тепер, коли я замислююся над цим, це звучить зовсім як рішення "дозволено повторити" з вашої відповіді, чи не так?
Бергі

countні O(1)(тобто може переповнитись)
reinierpost

1
@Eternalcode Я ніколи не говорив, що countце був int:-) Так, це не спрацює, але на Java це все одно не може статися
Бергі

12

Позначимо масиви через і припустимо, що вони мають довжину .нA,Bn

Припустимо спочатку, що значення у кожному масиві є різними. Ось алгоритм, який використовує простір :O(1)

  1. Обчисліть мінімальні значення обох масивів та перевірте, чи вони однакові.

  2. Обчисліть другі мінімальні значення обох масивів і перевірте, чи вони однакові.

  3. І так далі.

Обчислюючи мінімальне значення масиву, чітко використовується простір . Враховуючи й найменший елемент, ми можемо знайти -й найменший елемент, знайшовши мінімальне значення, що перевищує й найменший елемент (тут ми використовуємо той факт, що всі елементи відрізняються).k ( k + 1 ) kO(1)k(k+1)k

Коли елементам дозволено повторити, ми змінюємо алгоритм наступним чином:

  1. Обчисліть мінімальні значення обох масивів, підрахуйте, скільки разів кожен з'являється, і перевірте і чи підрахунки однакові . m A , 1 = m B , 1mA,1,mB,1mA,1=mB,1

  2. Обчисліть мінімальні значення більше у двох масивах (відповідно), і підрахуйте, скільки разів кожен з'являється. Перевірте, що , і що підрахунки однакові. m A , 1 , m B , 1 m A , 2 = m B , 2mA,2,mB,2mA,1,mB,1mA,2=mB,2

  3. І так далі.


1
Чи був би такий підхід оскільки здається, що єдиним способом пошуку елемента min у просторі та доступу лише до читання до масиву є перегляд всіх елементів? O ( 1 )O(n2)O(1)
ryan

4
Для цього потрібно впорядкувати алфавіт, хоча алгоритм легко змінити, щоб цього не вимагати. Однак у випадку "має дублікати" для цього потрібен пробіл , а не . Підрахунок займає місце. O ( 1 )O(lgn)O(1)
Дерек Елкінс покинув SE

7
Для підрахунку потрібен (логарифмічний) простір, але - за цим визначенням використання простору - так це навіть ітерація над масивом. Таким чином, під суворим значенням використання простору, немає можливості зробити це в постійному просторі.
Даніель Жур

4
@DanielJour, це залежить від того, яку модель витрат ви використовуєте. За однакової вартості це можливо в постійному просторі.
ryan

7
Якщо вам дозволено лише постійну кількість біт, ви можете обробляти лише алфавіти постійного розміру (це випливає з теорії регулярних мов).
Yuval Filmus

2

Визначте деяку функцію f (c), яка відображає деякий символ c до унікального простого числа (a = 2, b = 3, c = 5 тощо).

set checksum = 1
set count = 0 <-- this is probably not even necessary, but it's another level of check
for character c in string 1
    checksum = checksum * f(c)
    count = count + 1
for character c in string 2
    checksum = checksum / f(c)
    count = count = 1

permutation = count == 0 and checksum == 1

Лише заявити, що ви можете використовувати функцію відображення простих чисел, є трохи ручним рухом, і, швидше за все, там, де виникне проблема із збереженням простору .O(1)


f(c)O(1)O(1)

Ще одне питання, яке я зрозумів після публікації, - це те, що контрольна сума буде гігантським числом для великих рядків, настільки, що сама по собі вона може порушити вимогу про простір O (1). Це можна вирішити, використовуючи поплавці та змішуючи символ символом на одній рядку, потім розділяючи на інший, тоді просто кажучи, контрольна сума повинна бути близькою до 1. Струни повинні бути справді гігантськими, щоб помилка з плаваючою комою була проблемою.
Алекс Стассе

4
O(logn)

4
Θ(n)n

0

Ви можете зробити це так O(nlogn). Сортуйте два рядки та порівняйте їх за індексом. Якщо вони відрізняються де-небудь, вони не перестановки один одного.

Для O(n)рішення можна використовувати хешування. Ця функція хешування спрацювала, і eдля будь-якої літери було б її значення ascii. Якщо два хеша рядків різняться, вони не перестановки один одного.

Функція хешування за посиланням:

Один з потенційних кандидатів може бути таким. Зафіксуйте непарне ціле число R. Для кожного елемента e ви хочете хеш обчислити коефіцієнт (R + 2 * e). Потім обчисліть добуток усіх цих факторів. Нарешті розділіть продукт на 2, щоб отримати хеш.

Коефіцієнт 2 в (R + 2e) гарантує, що всі коефіцієнти непарні, отже, уникнення того, що продукт коли-небудь стане 0. Ділення на 2 в кінці, тому що продукт завжди буде непарним, отже, поділ просто видаляє постійний біт .

Наприклад, я вибираю R = 1779033703. Це довільний вибір, коли деякі експерименти повинні показувати, чи хороший чи поганий даний R. Припустимо, що ваші значення [1, 10, 3, 18]. Продукт (обчислюється за допомогою 32-бітових ints) є

(R + 2) * (R + 20) * (R + 6) * (R + 36) = 3376724311 Отже, хеш буде

3376724311/2 = 1688362155.

Використання подвійного хешування (або для надмірного знищення ще більше) шляхом зміни значення R успішно визначить їх як перестановки з дуже високою ймовірністю.


1
Ви не можете сортувати рядки, оскільки ви не можете їх змінювати. Що стосується хешування, то це рандомізований алгоритм, який може дати неправильну відповідь.
Yuval Filmus

0

Скажімо, у вас є два рядки під назвою s і t.

Ви можете використовувати евристику, щоб переконатися, що вони не є неоднаковими.

  1. s.length == т. довжина
  2. сума символів s == сума символів в t
  3. [те саме, що в 2. але з xor замість суми]

Після цього ви можете легко запустити алгоритм, щоб довести, що рядок є рівним.

  1. сортуйте один рядок, щоб він дорівнював іншому та порівняйте (O (n ^ 2))
  2. сортувати обидва та порівняти (O (2n log (n))
  3. перевірити для кожного знака в s, чи є однакові суми в обох рядках (O (n ^ 2))

Звичайно, ви не можете так швидко сортувати, якщо вам не дозволяється використовувати додаткове місце. Тож не має значення, який алгоритм ви обрали - кожен алгоритм знадобиться працювати в O (n ^ 2) час, коли є лише простір O (1), і якщо евристика не змогла довести, що вони не можуть бути рівними.


3
" Змінювати рядки не дозволяється жодним чином. "
Бергі

0

Код у стилі С для всього розпорядку:

for (int i = 0; i < n; i++) {
   int k = -1;
   next: for (int j = 0; j <= i; j++)
       if (A[j] == A[i]) {
          while (++k < n)
              if (B[k] == A[i])
                  continue next;
          return false; // note at this point j == i
       }
}
return true; 

Або в дуже багатослівному псевдокоді (використовуючи 1-індексацію)

// our loop invariant is that B contains a permutation of the letters
// in A[1]..A[i-1]
for i=1..n
   if !checkLetters(A, B, i)
      return false
return true

де функція checkLetters (A, B, i) перевіряє, що якщо в A [1] є M копій A [i], то A [i], то в B є принаймні M копії A [i]:

checkLetters(A,B,i)
    k = 0 // scan index into B
    for j=1..i
      if A[j] = A[i]
         k = findNextValue(B, k+1, A[i])
         if k > n
            return false
    return true

і функція findNextValue шукає в B значення, починаючи з індексу, і повертає індекс там, де воно було знайдено (або n + 1, якщо його не знайдено).

n2


Чи можете ви, будь ласка, перетворити свій код C у псевдокод? Це не сайт програмування.
Yuval Filmus

Це здається ще одним варіантом відповіді Берджі (з деякими непослідовними відмінностями).
Yuval Filmus

O(nm)O(n2)

0

O(n3n

Проведіть цикл string1і string2для кожного символу перевірте, наскільки часто його можна знайти в string1і string2. Я персонаж частіше в одному рядку, ніж в іншому, це не перестановка. Якщо частоти всіх символів рівні, то рядки є перестановками один одного.

Ось шматок пітона, щоб зробити це точно

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references string1 
    #  string2, it is not a copy
    for char in string:
      count1=0
      for char1 in string1:
        if  char==char1:
          count1+=1
      count2=0
      for char2 in string2:
        if  char==char2:
          count2+=1
      if count1!=count2:
        print('unbalanced character',char)
        return()
  print ("permutations")
  return()

check_if_permutations(s1,s2)

stringstring1string2charchar1char2O(logn)count1count2string[string1, string2]

Звичайно, вам навіть не потрібні підрахунки змінних, але ви можете використовувати вказівники.

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references one of string1 
    # or string2, it is not a copy
    for char in string:
      # p1 and p2 should be views as pointers
      p1=0
      p2=0
      while (p1<len(string1)) and (p2<len(string2)):
        # p1>=len(string1): p1 points to beyond end of string
        while (p1<len(string1)) and (string1[p1]!=char) :
          p1+=1
        while(p2<len(string2)) and (string2[p2]!=char):
          p2+=1
        if (p1<len(string1)) != (p2<len(string2)):
          print('unbalanced character',char)
          return()
        p1+=1
        p2+=1
  print ("permutations")
  return()

check_if_permutations(s1,s2)

O(log(n))

n


Це те саме, що рішення Бергі нижче.
Yuval Filmus

@YuvalFilmus Ні, він не повторюється по всьому алфавіту, тому його час виконання не залежить від розміру алфавіту. Він використовує лише два рядки, які слід перевірити. Також друга програма уникає підрахунку.
чудо173

@YuvalFilmus Я бачу, що ваші та інші коментарі спрямовані на те, як я використовував у своїй програмі.
чудо173
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.