Один елемент, який відрізняється двома масивами. Як знайти це ефективно?


22

Я готуюсь до інтерв'ю з кодування, і я не можу реально зрозуміти найефективніший спосіб вирішити цю проблему.

Скажімо, у нас є два масиви, що складаються з чисел, які не є несортованими. Масив 2 містить число, якого немає у масиві 1. Обидва масиви мають випадковим чином розміщені номери, не обов'язково в одному порядку або за однаковими індексами. Наприклад:

Масив 1 [78,11, 143, 84, 77, 1, 26, 35 .... п]

Масив 2 [11,84, 35, 25, 77, 78, 26, 143 ... 21 ... n + 1]

Який найшвидший алгоритм пошуку числа, яке відрізняється? Який його час роботи? У цьому прикладі число, яке ми шукали б - 21.

Моя ідея полягала в тому, щоб пробігти через масив 1 і видалити це значення з масиву 2. Ітерацію, поки ви не закінчите. Це має бути приблизно час роботи , правда?O(nlogn)


@Jandvorak Дякую, хлопці, за відповіді. Я спізнився і трапилось заснути після цього. Масив несортований, і всі елементи з'являються у випадкових індексах в обох масивах.
Костянтино Спаракіс

@KonstantinoSparakis: це уточнення недійсне для відповідей, які передбачають, що обидва масиви містять елементи в одних і тих же позиціях.
Маріо Червера

Перехресне опублікування нахмуриться на softwareengineering.stackexchange.com/users/256931/…
папараццо

@Paparazzi Просто шукав рішення, яке я прочитав у мета-інженерії програмного забезпечення - було куди йти, щоб отримати рішення, але в той час я не знав про CS-форум. Я сповістив модників, щоб почистити його.
Костянтино Спаракіс

@ Paparazzi чи є резервна копія мета-повідомлення? Я особисто не бачу жодного способу добре реалізувати цю політику.
djechlin

Відповіді:


30

Я бачу чотири основні способи вирішення цієї проблеми з різним часом роботи:

  • рішення: це було б рішення, яке ви пропонуєте. Зауважте, що оскільки масиви несортовані, видалення займає лінійний час. Ви здійснюєте n вилучень; тому цей алгоритм займає квадратичний час.O(n2)n

  • рішення: попередньо відсортуйте масиви; потім виконайте лінійний пошук, щоб визначити виразний елемент. У цьому рішенні на час роботи переважає операція сортування, звідси O ( nO(nlogn) верхня межа.O(nlogn)

Коли ви визначаєте рішення проблеми, завжди слід запитати себе: чи можу я зробити краще? У цьому випадку ви можете, розумно використовуючи структури даних. Зауважте, що все, що вам потрібно зробити, - це повторити один масив і виконати повторні пошуки в іншому масиві. Яка структура даних дозволяє робити пошук у (очікуваний) постійний час? Ви правильно здогадалися: хеш-таблиця .

  • рішення (очікуване): ітерація першого масиву та збереження елементів у хеш-таблиці; потім виконайте лінійне сканування у другому масиві, шукаючи кожен елемент у хеш-таблиці. Поверніть елемент, який не знайдено в хеш-таблиці. Це лінійно-часове рішення працює для будь-якого типу елементів, який можна передати хеш-функції (наприклад, він би працював аналогічно для масивів рядків).O(n)

Якщо ви хочете, щоб гарантії верхньої межі були, а масиви строго складаються з цілих чисел, найкращим рішенням є, мабуть, рішення, запропоноване Тобі Алафіном (навіть якщо це рішення не дасть вам індекс елемента, який відрізняється у другому масиві) :

  • рішення (гарантоване): підсумовуйте елементи першого масиву. Потім підсумуйте елементи другого масиву. Нарешті, виконайте субстракцію. Зауважте, що це рішення може бути фактично узагальнено до будь-якого типу даних, значення якого можна представити у вигляді бітових рядків фіксованої довжини, завдякипобітовому оператору XOR. Це ґрунтовно пояснено увідповідіІльмарі Каронен. O(n)

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


4
підсумовування не є лінійним, якщо числа одержують досить великі розміри.
Сордж Борщ

9
Одна гарна річ алгоритму підсумовування полягає в тому, що він працює з будь-якою абелевою групою, а не тільки з цілими числами (Найбільш помітно uint64; cc @sarge).
Джон Дворак

6
@Abdul річ у тому, що якщо ваші цілі числа дуже великі, ви більше не можете робити вигляд, що вони приймають для додавання. Я вважаю, що складність зростає до O ( n ln n ), якщо це враховувати. Використання XOR замість звичайного додавання вирішує це, хоча, все ж допускаючи довільно велику кількість вхідних даних. O(n)O(nlnn)
Джон Дворак

2
@JanDvorak Ні, це не так. Ви припускаєте, що операція, визначена на абелевій групі, потребує постійного часу. Це не можна просто припустити.
UTF-8

2
@ UTF-8 Я цього не припускаю. Але це робиться у скінченних групах (uint64), а додавання на місці цифр (додавання у ) лінійне за розміром операнду, що не знаходиться на місці. Отже, обчислення суми в таких групах є лінійним часом у загальному розмірі операндів. Znd
Джон Дворак

16

різницеві через сум рішення , запропоноване Tobi і Маріо насправді може бути узагальнений на будь-який інший тип даних , для якого ми можемо визначити (постійне час) бінарна операцію , яке:Θ(n)

  • всього , таким чином, що при будь-яких значеннях і б , б визначена і ті ж типу (або , щонайменше , деякі відповідного надтип нього, для якого оператор по - , як і раніше визначається);abab
  • асоціативний , такий, що ;a(bc)=(ab)c
  • комутативний , такий, що ; іab=ba
  • скоротна , таким чином, що існує зворотний оператор , який задовольняє умові ( б ) б = . Технічно ця обернена операція навіть не обов'язково повинна бути постійною за часом, доки "віднімання" двох сум з n елементів не займе більше, ніж O ( n ) часу.(ab)b=anO(n)

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

З допомогою такої операції , ми можемо визначити «суму» масиву а = ( 1 , 2 , ... , н ) як ( a=(a1,a2,,an) З огляду наінший масив Ь = ( Ь 1 , б 2 , ... , б п , б п + 1 ) , що містить всі елементиплюс один додатковий елемент х , митаким чиномє (

(a)=a1a2an.
b=(b1,b2,,bn,bn+1)ax , і тому ми можемо знайти цей додатковий елемент, обчисливши: x = ( (b)=(a)x
x=(b)(a).

Наприклад, якщо значення масивів є цілими числами, то ціле додавання (або модульне додавання для цілих чисел кінцевої довжини) може використовуватися як оператор , з відніманням як обернена операція . Як альтернативи, для будь-якого типу даних, значення яких можуть бути представлені в вигляді бітових рядків фіксованої довжини, ми можемо використовувати побітовое XOR і як і .

Більш загально, ми можемо навіть застосувати побітовий метод XOR до рядків змінної довжини, додавши їх до тієї самої довжини, скільки потрібно, доки у нас є спосіб реверсивно видалити оббивки в кінці.

У деяких випадках це банально. Наприклад, нульові завершені рядки байтів, що закінчуються C, неявно кодують власну довжину, тому застосування цього методу для них є тривіальним: коли XORing два рядки, прокладіть коротший з нульовими байтами, щоб їх довжина збігалася, і обріжте будь-які додаткові кінцеві нулі з кінцевий результат. Зауважте, що проміжні рядки суми XOR можуть містити нульові байти, тому вам потрібно буде чітко зберігати їх довжину (але вам потрібно лише максимум один або два).

1001232байтів у довжину, ми могли б кодувати довжину кожного рядка як 32-бітове ціле число і додавати його до рядка. Або ми могли навіть кодувати довільну довжину рядків за допомогою деякого коду префікса і додавати їх до рядків. Існують і інші можливі кодування.

Θ(n)

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


Вау дуже цікаво взяти на це. Дякую @IlmariKaronen
Костянтино Спаракіс

14

Я б опублікував це як коментар до відповіді Тобі, але у мене поки що немає репутації.

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

Просто обчисліть xor-суму (тобто x [0] ^ x [1] ^ x [2] ... x [n]) кожного списку, а потім xor ці два значення. Це дасть вам значення стороннього елемента (але не індексу).

Це все ще O (n) і дозволяє уникнути будь-яких проблем із переповненням.


3
Я також би використовував XOR, оскільки він здається трохи охайнішим, але, справедливо кажучи, переповнення насправді не є проблемою, поки мова, яку ви реалізуєте це, підтримує переповнення шляхом обгортання.
Мартін Ендер

14

Елемент = Сума (масив2) - сума (масив1)

Я щиро сумніваюся, що це найбільш оптимальний алгоритм. Але це ще один спосіб вирішити проблему, і це найпростіший спосіб її вирішити. Сподіваюся, це допомагає.

Якщо кількість доданих елементів більше однієї, це не працюватиме.

Моя відповідь має однакову складність часу для найкращого, гіршого та середнього випадку,

РЕДАКЦІЯ
Подумавши, я думаю, що моя відповідь - це ваше рішення.

nn11=n12=n+11=n

2n121=1

2n1+1=2n

Θ(n)

EDIT:
Через деякі проблеми з типами даних, сума XOR , запропонована reffu, буде більш доцільною.


Зауважте, що цей метод може не дати точної відповіді, якщо ваші значення плавають, оскільки підсумовування чисел може ввести помилки округлення. Він буде працювати для цілих значень, але за умови, що або: а) цілий тип має чітко визначену поведінку при перекритті при переливанні, або б) ви зберігаєте суми у змінних типу, достатньо широких, щоб вони не могли переповнювати.
Ільмарі Каронен

Клас "BigNum" Рубі, ймовірно, може впоратися з цим.
Тобі Алафін

Він абсолютно не працює, якщо ваш масив містить, наприклад, рядки або майже все, що неможливо додати змістовно.
gnasher729

Так, я зрозумів. Що з використанням "XOR"? Чи буде це працювати для поплавків?
Тобі Алафін

Так, і також вказівники і взагалі все, що складається з фіксованого числа бітів. Багато мов цього не підтримують, але це не є принциповою проблемою. Модульне додавання / віднімання працюватиме в тих же випадках.
Гарольд

1

Якщо припустити, що масив 2 був створений, взявши масив 1 і вставивши елемент у випадковій позиції, або масив 1 був створений, взявши масив 2 і видаливши випадковий елемент.

Якщо гарантується, що всі елементи масиву є різними, час - O (ln n). Ви порівнюєте елементи у місці n / 2. Якщо вони рівні, додатковий елемент знаходиться від n / 2 + 1 до кінця масиву, інакше - від 0 до n / 2. І так далі.

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

PS. Оскільки вимоги змінилися, перевірте свою бібліотеку на наявність. У macOS / iOS ви створюєте NSCountSet, додаєте всі числа з масиву 2, видаляєте всі номери з масиву 1, і що залишилося - це все, що є в масиві 2, але не в масиві 1, не покладаючись на твердження, що є ще один додатковий пункт.


Ця відповідь була точковою, але питання було відредаговано з новою вимогою, яка визнає недійсним ваше припущення.
Маріо Червера

Ваша нова відповідь здається правильною. Яка складність у часі.
Тобі Алафін

Ну, по-перше, який час потрібен для написання коду. Це банально. NSCountSet використовує хешування, тому складність часу "зазвичай лінійна".
gnasher729

-1

вар найкоротший, найдовший;

Перетворити найкоротше на карту для швидкого посилання та циклу протягом найдовшого, поки поточне значення не буде на карті.

Щось подібне у javascript:

якщо (arr1.length> arr2.length) {найкоротший = arr2; найдовший = arr1; } else {найкоротший = arr1; найдовший = arr2; }

var map = shorttest.reduce (функція (obj, значення) {obj [value] = true; return obj;}, {});

var різниця = найдовше.find (функція (значення) {return !!! map [value];});


Коди без пояснень тут не вважаються гарною відповіддю. Крім того, навіщо вам користуватися !!! ?
Зло

-1

О (N) рішення у часовій складності O (1) з точки зору складності простору

Постановка проблеми: якщо припустити, що array2 містить усі елементи масиву1 плюс ще один елемент, який не присутній у масиві1.

Рішення полягає в тому, що ми використовуємо xor для пошуку елемента, якого немає в масиві1, тому кроки: 1. Почніть з масиву1 та зробіть xor усіх елементів і збережіть їх у змінній. 2. Візьміть array2 і зробіть xor всіх елементів зі змінною, яка зберігає xor масиву1. 3. Після виконання операції наша змінна буде містити елемент, який присутній лише у масиві2. Наведений вище алгоритм працює через наступну властивість xor "a xor a = 0" "a xor 0 = a" Я сподіваюся, що це вирішить вашу проблему. Також вище запропоновані рішення також чудово

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