Як знайти середній елемент пов'язаного списку за один прохід?


12

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


5
Якщо відповіді, представлені в цій темі, - це те, чого очікував інтерв'юер, то це питання не перевіряє технічних можливостей, але наскільки добре кандидат може ухилитися, як юрист.
Г. Бах

2
Це жахливе питання про інтерв'ю, оскільки воно критично залежить від терміна " пропуск ", який є розпливчастим, неоднозначним, суб'єктивним. Майже всі хороші відповіді на це питання передбачають зловживання визначенням, щоб ви могли ефективно його ігнорувати.
RBarryYoung

2
Що ж, питання викликає тут багато дискусій. Це означає, що це гарне запитання про інтерв'ю в одному відношенні: воно починає думати.
Гендрик Ян

3
Я так хочу відповісти "прочитайте всі елементи у векторному, потім перейдіть до елемента розміром позиції () / 2"
Cort Ammon

1
@HendrikJan Власне, я думаю, що це абсолютно жахливе питання інтерв'ю. По-перше, це, ймовірно, призведе до аргументів про те, що саме означає "за один прохід", а не продуктивного обговорення. По-друге, кандидат міг з'ясувати "правильну" відповідь, а потім відхилити її, оскільки вважав, що це порушує критерій "за один прохід". По-третє, оскільки це відоме питання, це кращий тест "Чи знаєте ви популярні питання інтерв'ю?" ніж "Ти добре підходить для цієї роботи?" Будь-якого з них повинно бути достатньо, щоб занурити це як питання; всі три одночасно є катастрофою.
Девід Річербі

Відповіді:


24

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

Це можна зробити за одним пов'язаним списком, з приємною хитрістю. Два вказівники подорожують над списком, один із подвійною швидкістю. Коли швидкий доходить до кінця, інший - на півдорозі.


1
Так, незрозуміло, чи це один пропуск. Питання щодо цього незрозуміле.
Девід Річербі

2
До речі, це пов’язано з головоломкою, де у вас дві свічки, одна година горить кожна, і вас просять відміряти 45 хвилин.
Гендрік Ян

2
Чи насправді це інакше, ніж повторення списку, підрахунок елементів, а потім повторення вдруге до точки в половину шляху? Все, що відрізняється, це коли ви повторите додаткову половину. Як згадує @RBarryYoung в іншій подібній відповіді, це насправді не один пропуск, це пропуск і півтора.

2
Якщо список довгий, переміщення обох покажчиків "паралельно" призведе до меншої кількості пропусків кешу, ніж повторення з початку вдруге.
zwol

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

7

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

Просте, майже нерозумне рішення - це лише наростити середній вузол кожні два вузли

function middle(start) {
    var middle = start
    var nextnode = start
    var do_increment = false;
    while (nextnode.next != null) {
        if (do_increment) {
             middle = middle.next;
        }
        do_increment = !do_increment;
        nextnode = nextnode.next;
    }
    return middle;
}

Ваш другий варіант - правильна відповідь (звичайно, ІМХО).
Меттью Крамлі

1
Це фактично робить 1 1/2 пропуску через пов'язаний список.
RBarryYoung

6

Розвиваючи відповідь Гендрика

Якщо це подвійний список, повторіть його з обох кінців

function middle(start, end) {
  do_advance_start = false;
  while(start !== end && start && end) {
     if (do_advance_start) {
        start = start.next
     }
     else {
        end = end.prev
     }
     do_advance_start = !do_advance_start
  }
  return (start === end) ? start : null;
}

Дано [1, 2, 3] => 2

1, 3
1, 2
2, 2

Дано [1, 2] => 1

1, 2
1, 1

Дано [1] => 1

Дано [] => null


Наскільки це ефективно? Ви також повторюєте n разів, а не n / 2.
Каран Ханна

3

Створіть структуру з покажчиком, здатним вказувати на вузли зв'язаного списку та з цілою змінною, яка зберігає підрахунок кількості вузлів у списку.

struct LL{
    struct node *ptr;
    int         count;
}start;

start.ptrstart.count=1
start.count

start.count


-2

Створіть динамічний масив, де кожен елемент масиву є вказівником на кожен вузол у списку в порядку проходження, починаючи з початку. Створіть ціле число, ініціалізоване до 1, яке відстежує кількість відвідуваних вами вузлів (з кроком кожного разу, коли ви переходите до нового вузла). Коли ви доходите до кінця, ви знаєте, наскільки великий список, і у вас є впорядкований масив покажчиків на кожен вузол. Нарешті, розділіть розмір списку на 2 (і відніміть 1 для індексації на основі 0) і отримайте вказівник, що знаходиться у цьому індексі масиву; якщо розмір списку непарний, ви можете вибрати, який елемент повернути (я все одно поверну перший).

Ось який-небудь код Java, який отримує крапку (хоча ідея динамічного масиву буде трохи химерною). Я б запропонував C / C ++, але я дуже іржавий у цій галузі.

public Node getMiddleNode(List<Node> nodes){

    int size = 1;
    //add code to dynamically increase size if at capacity after adding
    Node[] pointers = new Node[10];

    for (int i = 0; i < nodes.size(); i++){
        //remember to dynamically allocate more space if needed
        pointers[i] = nodes.get(i);
        size++;
    }

    return pointers[(size - 1)/2];

}

-3

Чи вважається рекурсія більш ніж одним проходом?

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

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

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

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

Я майже завжди відповідаю на запитання інтерв'ю ще більше запитань. Якщо у мене з’явиться таке запитання (у мене ніколи не було), я б запитав (1) Що ви зберігаєте в цьому пов'язаному списку, і чи є більш відповідна структура для ефективного доступу до середнього вузла, якщо це дійсно потрібно зробити? ; (2) Які мої обмеження? Я можу зробити це швидше, якщо пам'ять не є проблемою (наприклад, відповідь масиву), але якщо інтерв'юер вважає, що накопичення пам’яті марнотратне, я занурююся. (3) На якій мові я буду розвиватися? Майже в кожній сучасній мові, яку я знаю, є вбудовані класи для роботи з пов’язаними списками, які роблять пересування списку непотрібним - навіщо винаходити щось, що було налаштовано на ефективність розробниками мови?


6
Ви не знаєте, хто сприяв чомусь, тому ваш висновок про те, що одна людина зголосився на все, може бути, а може і не бути правдою, але це, безумовно, не має підстав для наявних у вас фактів. Але я відповідаю вашій відповіді, тому що ми шукаємо пояснення, а не купи коду.
Девід Річербі

Це може бути припущенням, але коли я дивлюсь одну мить і менше ніж через 30 секунд, кожен пост має рівно -1, це не необгрунтовано. Навіть якщо це було 5 чи 6 різних людей, ніхто з них не залишив коментар, чому. Але дякую за принаймні зазначення причини. Я не розумію, чому багатослівне пояснення краще, ніж код - так, це для телефонного інтерв'ю, але я не даю ОП консервовану відповідь на відрижку, я показую йому спосіб зробити це. IE Я думаю, що заборонив публікацію, тому що в ній код не викликається, але дякую, що принаймні сказав, чому ти це зробив.
Джеймс К

Справедливий момент - мені не прийшло в голову, що ви можете знати терміни голосування (завантаження сторінки, поки вони трапляються, я думаю, єдиний спосіб, як звичайні користувачі, як ми, могли це дізнатись). У нас є кілька мета-повідомлень про те, чому ми намагаємось тут уникати фактичного коду: (1) (2) .
Девід Річербі

Дякую за метапосилання. Я читав FAQ, але нічого там не бачив - не те, щоб я помітив щось подібне, швидше за все, не те, чого я б очікував. Але я нічого не міг знайти, коли повторно перевірив, після того, як занурився. Я, можливо, все ще не помічаю цього, але я виглядав. Міркування в мета-повідомленнях мають сенс; дякую за відповідь.
James K

-5

За допомогою 2 покажчиків. Збільшення по одному при кожній ітерації та інше при кожній другої ітерації. Коли 1-й покажчик вказує на кінець пов'язаного списку, другий вказівник вказує на середній режим пов'язаного списку.


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