Как найти средний элемент связанного списка за один проход?


12

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


5
Если ответы, представленные в этой теме, соответствуют ожиданиям интервьюера, тогда этот вопрос не проверяет технические возможности, а показывает, насколько хорошо кандидат может увернуться, как юрист.
Г. Бах

2
Это ужасный вопрос для интервью, потому что он критически зависит от термина « проход », который является расплывчатым, двусмысленным, субъективным. Почти все хорошие ответы на этот вопрос включают злоупотребление определением, чтобы вы могли эффективно его игнорировать.
RBarryYoung

2
Ну, вопрос вызывает много дискуссий здесь. Это означает, что это хороший вопрос для собеседования в одном отношении: он заставляет задуматься.
Хендрик Ян

3
Я так хочу ответить «прочитать все элементы в вектор, а затем получить доступ к элементу в позиции размера () / 2»
Cort Ammon

1
@HendrikJan На самом деле, я думаю, что это совершенно ужасный вопрос для интервью. Во-первых, это может привести к спорам о том, что именно означает «за один проход», а не к продуктивной дискуссии. Во-вторых, кандидат может выяснить «правильный» ответ, а затем отклонить его, поскольку считает, что он нарушает критерий «за один проход». В-третьих, поскольку это общеизвестный вопрос, это лучший тест «Знаешь ли ты популярные вопросы для интервью?» чем "Вы подходите для этой работы?" Любого из них должно быть достаточно, чтобы погрузить это в вопрос; все три одновременно являются катастрофой.
Дэвид Ричерби

Ответы:


24

Обманывая и делая два прохода одновременно, параллельно. Но я не знаю, понравится ли это рекрутерам.

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


1
Да, неясно, это единственный проход. Вопрос по этому вопросу неясен.
Дэвид Ричерби

2
Кстати, это связано с головоломкой, где у вас есть две свечи, по одному часу, и каждая из вас попросила измерить 45 минут.
Хендрик Ян

2
Действительно ли это отличается от повторения списка, подсчета элементов, а затем повторения второй раз до половины пути? Все иначе, когда вы перебираете лишнюю половину. Как @RBarryYoung упоминает в другом подобном ответе, это действительно не единственный проход, это проход с половиной.

2
Если список длинный, перемещение обоих указателей «параллельно» приведет к меньшему количеству пропусков кэша, чем повторение с начала во второй раз.
Звол

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

Считается ли рекурсия более чем одним проходом?

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

На последнем узле разделите счет на два и truncate / floor () результат (если вы хотите, чтобы первый узел был «средним», когда имеется только два элемента) или округлите (если вы хотите, чтобы второй узел был середина"). Используйте индекс на основе нуля или единицы соответственно.

Разматывая, сопоставьте количество ссылок с локальной копией (которая является номером узла). Если равно, верните этот узел; иначе вернуть узел, возвращенный из рекурсивного вызова.
,

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

Я почти всегда отвечаю на вопросы интервью с большим количеством вопросов. Если я получу такой вопрос (у меня его никогда не было), я бы спросил: (1) Что вы храните в этом связанном списке, и существует ли более подходящая структура для эффективного доступа к среднему узлу, если действительно есть необходимость в этом? ; (2) Каковы мои ограничения? Я могу сделать это быстрее, если память не является проблемой (например, ответ массива), но если интервьюер думает, что накопление памяти расточительно, я потерял сознание. (3) На каком языке я буду развиваться? Почти у каждого современного языка, который я знаю, есть встроенные классы для работы со связанными списками, которые делают обход списка ненужным - зачем изобретать что-то, что было настроено для эффективности разработчиками языка?


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

Это может быть предположение, но когда я смотрю один момент и менее чем через 30 секунд, каждое сообщение имеет ровно -1, это не является необоснованным. Даже если это были 5 или 6 разных людей, ни один из них не оставил комментарий, почему. Но спасибо, по крайней мере, за указание причины. Я не понимаю, почему многословное объяснение лучше, чем код - да, это для телефонного интервью, но я не даю ФП законный ответ, чтобы извергнуть, я показываю ему способ сделать это. То есть, я думаю, что публикация сообщения, потому что в нем есть код, не нужна, но спасибо, что вы хотя бы сказали, почему вы это сделали.
Джеймс К

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

Спасибо за мета ссылки. Я прочитал FAQ, но ничего там не увидел - не то, чтобы я заметил что-то подобное, скорее всего, не то, чего я ожидал. Но я ничего не мог найти, когда перепроверил после того, как меня обманывают. Возможно, я все еще не замечаю этого, но я посмотрел. Рассуждения в мета-постах имеют смысл; Спасибо за ответ.
Джеймс К

-5

Используя 2 указателя. Увеличивайте одно на каждой итерации, а другое - на каждой второй итерации. Когда первый указатель указывает на конец связанного списка, второй указатель указывает на средний режим связанного списка.


Это просто дублирует ответ Хендрика . Пожалуйста, не отвечайте, если у вас нет чего-то нового, чтобы сказать.
Дэвид Ричерби
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.