Найдите средний список неизвестных размеров
В недавнем интервью меня спросили:
Найдите средний элемент отсортированного списка неизвестной длины, начиная с первой позиции.
Я ответил следующим образом:
Имейте 2 счетчика позиции:
счетчик1
Counter2
Увеличить счетчик1 на 1 и счетчик2 на 2. Когда счетчик 2 достигнет конца счетчика 1, он будет посередине. Я чувствую, что это не эффективно, потому что я пересматриваю узлы, которые я уже видел. В любом случае, существует ли более эффективный алгоритм?
Ответы
Ответ 1
Предполагая связанный список, вы можете сделать это при посещении произвольно близкого к N элемента.
Сделать 5/4 N:
- Итерации по списку, пока вы не нажмете на конец, считая элементы.
- Снимите якорь при каждой мощности 2-го элемента. Отслеживать последние 2 якоря.
- Итерируйте предыдущий якорь, пока он не достигнет середины списка.
Когда вы попадаете в конец списка, предыдущий якорь находится до середины, но, по крайней мере, на полпути. Таким образом, N для полной итерации + не более 1/4 N для якоря = 5/4 N.
Уменьшение якорей более часто, например, при каждой мощности потолка 1.5-го элемента, приближает вас к N по мере необходимости (за счет отслеживания большего количества якорей, но для любого заданного X в качестве шага мощности асимптотическая память постоянна).
Ответ 2
Я предполагаю, что вы обсуждаете связанный список. Действительно, ваше решение отличное. Альтернативой было бы просто пройти список, подсчитывающий количество элементов, а затем начать с самого начала, пройдя половину подсчитанной суммы. Оба метода заканчивают перемещение узлов 3n/2
, поэтому нет большой разницы.
Возможно, могут быть небольшие преимущества кэша для любого метода в зависимости от архитектуры; первый способ может иметь преимущество в использовании кэшированных узлов, что может означать более быстрое извлечение, если кеш достаточно велик, прежде чем два указателя находятся на расстоянии друг от друга. В качестве альтернативы, кеш может получить лучшие блоки, если мы перейдем к списку за один раз, а не к сохранению двух указателей.
Ответ 3
Предполагая, что вы можете обнаружить, что вы находитесь за пределами списка и быстро ищете произвольную позицию в списке, вы можете удвоить предполагаемую длину списка (предположим, длина равна 1, затем 2, затем 4,...) до тех пор, пока вы не закончите конец списка, а затем используйте двоичный поиск между последней попыткой, которая меньше длины списка, и первое значение, которое превысило конец списка, чтобы найти фактический конец список.
Затем найдите позицию END_OF_LIST/2.
Таким образом, вам не нужно посещать каждый node.
Ответ 4
Технически вы можете сделать это за один проход, если используете память O (N) (при условии, что вы используете связанный список).
- Переместите список и преобразуйте его в массив (указателей, если это связанный список).
- Возврат arr [N/2]
Изменить: мне действительно нравится ваш ответ!
Ответ 5
Предполагая регулярный связанный с памятью список, который позволяет читать следующий элемент, учитывая ссылку на текущее несколько раз (отказ от ответственности, не проверенный, но идея должна работать):
// assume non-empty list
slow = fast = first;
count = 0;
while (fast)
{
fast = fast.next;
if (!fast)
break;
count++;
fast = fast.next;
if (fast)
count++;
slow = slow.next;
}
if (count % 2)
return slow.data;
else
return (slow.data + slow.next.data)/2.0;
Более сложным является случай, когда "список" не является связанным списком в памяти, а скорее потоком, который вы можете прочитать в отсортированном порядке и что читать каждый элемент только один раз, для которого у меня нет хорошего решения.