задачка (программистское)
Jan. 5th, 2011 04:08 pmЗадачка для программистов и интересующихся. Наверняка известная, но мне в такой форме не встречалась, и понравилась.
Дан массив размером N, в нем есть только числа от 1 до N-1 (необязательно все, необязательно по порядку). Очевидно, какие-то из них повторяются. Найти какое-то число, которое встречается в массиве больше одного раза.
Суть в том, чтобы сделать это с как можно лучшей сложностью времени и места. Скажем, тривиально сделать это за O(N) времени с O(N) места. Можно лучше. Исходный массив не бесплатный: его можно менять, но это считается в бюджет места.
Update: все скрытые комментарии с правильными решениями раскрываются. Не заглядывайте в комментарии, если хотите дальше думать сами - там есть много правильных решений. Оптимальное решение этой проблемы выполняет задачу за O(N) времени, O(1) места.
Дан массив размером N, в нем есть только числа от 1 до N-1 (необязательно все, необязательно по порядку). Очевидно, какие-то из них повторяются. Найти какое-то число, которое встречается в массиве больше одного раза.
Суть в том, чтобы сделать это с как можно лучшей сложностью времени и места. Скажем, тривиально сделать это за O(N) времени с O(N) места. Можно лучше. Исходный массив не бесплатный: его можно менять, но это считается в бюджет места.
Update: все скрытые комментарии с правильными решениями раскрываются. Не заглядывайте в комментарии, если хотите дальше думать сами - там есть много правильных решений. Оптимальное решение этой проблемы выполняет задачу за O(N) времени, O(1) места.
no subject
Date: 2011-01-05 02:44 pm (UTC)no subject
Date: 2011-01-05 02:54 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 02:55 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-05 05:01 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-05 05:18 pm (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2011-01-05 05:32 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-05 05:50 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-05 07:36 pm (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-06 12:55 pm (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2011-01-06 01:52 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-06 04:48 pm (UTC) - Expand(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:длина цикла
From:Re: длина цикла
From: (Anonymous) - Date: 2011-01-07 06:51 pm (UTC) - ExpandRe: длина цикла
From: (Anonymous) - Date: 2011-01-07 07:13 pm (UTC) - Expandалгоритм с двумя указателями
From:вот этот алгоритм
From: (Anonymous) - Date: 2011-01-07 07:58 pm (UTC) - Expandдвойной шаг
From:Re: двойной шаг
From: (Anonymous) - Date: 2011-01-07 09:34 pm (UTC) - ExpandRe: длина цикла
From:многократные повторения
From:Re: многократные повторения
From:самый общий случай
From:Re: самый общий случай
From:(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-07 10:56 pm (UTC) - ExpandТривиальное не-решение:
Date: 2011-01-05 03:16 pm (UTC)for(i = 0; i < N; i++)
{
Q = 1 << arr[i];
if( N & P )
{
// Нашли дупликат
return i;
}
else
{
N &= P;
}
}
(понятно, что P всё равно должна быть размером O(N)).
Re: Тривиальное не-решение:
Date: 2011-01-05 03:18 pm (UTC)no subject
Date: 2011-01-05 03:25 pm (UTC)no subject
Date: 2011-01-05 03:49 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 03:31 pm (UTC)понятное дело, в любом из этих списков есть цикл, причем длины меньше N. то есть есть элемент, на который указывают два указателя — последний в цикле и тот, что перед первым. эти два и будут повторяющимися элементами. а находить циклы в связных списках мы, слава заратустре, умеем (еще одна задача с интервью, пускаем два указателя с разными скоростями). чтобы не начать случайно изнутри цикла, начнем с последнего элемента, на него точно ничего не указывает.
итого О(1) памяти, O(N) времени. быстрее нельзя.
no subject
Date: 2011-01-05 03:40 pm (UTC)(no subject)
From: (Anonymous) - Date: 2011-01-05 04:21 pm (UTC) - Expand(no subject)
From:no subject
Date: 2011-01-05 03:52 pm (UTC)int index, int tmp,
index = 0;
tmp = A[index];
while( index != tmp ){
index = tmp;
tmp = A[index];
}
return tmp;
}
no subject
Date: 2011-01-05 03:54 pm (UTC)(no subject)
From: (Anonymous) - Date: 2011-01-05 04:03 pm (UTC) - Expand(no subject)
From: (Anonymous) - Date: 2011-01-05 04:12 pm (UTC) - Expand(no subject)
From:(no subject)
From: (Anonymous) - Date: 2011-01-05 05:25 pm (UTC) - Expand(no subject)
From:За один проход, с одной переменной
Date: 2011-01-05 04:19 pm (UTC)При этом гарантированно находим число которое встречается чаще всего, что ещё круче чем исходное условие.
Ну и ещё и алгоритм - проще некуда
Человек который его придумал - гений.
Re: За один проход, с одной переменной
Date: 2011-01-05 04:28 pm (UTC)Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:Re: За один проход, с одной переменной
From:no subject
Date: 2011-01-05 04:40 pm (UTC)no subject
Date: 2011-01-05 04:42 pm (UTC)Задачку видел раньше, но не думал; что-то пока не могу придумать, как сделать O(1) памяти (как хорошо, что вы прячете решения :).
no subject
Date: 2011-01-05 04:43 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 05:01 pm (UTC)Создать счетчики для элеметов в [0, sqrt(n)), [sqrt(n), 2*sqrt(n)), ..., [(sqrt(n)-1))*sqrt(n)...n),
Пройти по массиву обновляя счетчики, и найти тот интервал, в котором больще, чем sqrt(n) элеметов. Дальше просто переписать эти эелемнты , отсортировать, и найти нужный.
O(n log n) времени:
Та же идея, но с интервалами [0, n/2), [n/2, n). В одном из них будет больше чем n/2 элементов, а в другом меньше. Потом просто рекусивно искать.
no subject
Date: 2011-01-05 05:06 pm (UTC)Ой, не получится, все равно их слишком много.
(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 05:10 pm (UTC)no subject
Date: 2011-01-05 05:12 pm (UTC)no subject
Date: 2011-01-05 05:43 pm (UTC)вроде получилось O(n) времени и O(1) места :)
no subject
Date: 2011-01-05 05:46 pm (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 06:20 pm (UTC)int xorArray = 0;
for (int i = 0; i < N; i++) { xorAll ^= i; } // это можно и умнее посчитать, без цикла
for (int value : array) { xorArray ^= value; }
int answer = xorAll ^ xorArray;
no subject
Date: 2011-01-05 06:22 pm (UTC)Вы упустили то, что не все числа от 1 до N-1 должны быть в массиве, повторов может быть много разных.
(no subject)
From:no subject
Date: 2011-01-05 06:23 pm (UTC)рассмотрим записанные в массиве числа, как адреса в том же массиве, тогда из нуля у нас растет однонаправленный список, который зацикливается, и начало его не входит в цикл.
пошлем бегать по списку традиционно два указателся , один с единичной скоростью, другой с двойной. за о(н) один нагонит другой. далее еще за о(н) мы найдем длину цикла, пусть она равна м. пустим из нуля два указателя с единичной скоростью, но со сдвигом в м. в какой-то момент (еще о(н)) они будут показывать на одно число из разных мест.
no subject
Date: 2011-01-05 06:31 pm (UTC)no subject
Date: 2011-01-05 06:44 pm (UTC)int found = 0;
for (int i = 0; i < N; i++)
{
int value = Math.abs(arr[i]);
if (arr[value] < 0) { found = value; break; }
arr[value] = -arr[value];
}
no subject
Date: 2011-01-05 06:54 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2011-01-05 07:16 pm (UTC)Примитивно, конечно, но я ж не программист. Макро еще могу состряпать, а если требуется скрипт, то я выбрасываю такой софт, как непригодный к практическому использованию в реальных условиях.
no subject
Date: 2011-01-05 07:20 pm (UTC)(no subject)
From:no subject
Date: 2011-01-05 07:19 pm (UTC)Или же мы считаем, что числа невелики, заведомо влезают в какой-нибудь регистр, и все операции занимают один шаг?
no subject
Date: 2011-01-05 07:22 pm (UTC)Тут есть определенное противоречие, но на практике это неплохо работает.
Есть еще один смешной способ
Date: 2011-01-05 07:57 pm (UTC)Допустим, что повторяющихся чисел в массиве нет. Следовательно, если к каждому числу массива поочередно добавить все числа, присутствующие в массиве, то мы получим новый массив, в котором на каждом месте будет сумма E всех чисел от 1 до N-1, исключая те, которые не присутствуют в массиве. Можно и умножать вместо сложения, тогда у нас будет факториал F на каждом месте (возможно, не полный).
Если же в в массиве есть повторяющиеся числа, то, по крайней мере, два числа в конечном массиве будут отличаться от суммы Е (или факториала F). Далее уже просто.
То есть, наоборот...
Date: 2011-01-05 09:08 pm (UTC)no subject
Date: 2011-01-05 08:25 pm (UTC)Time: O(N)
Space: O(1)
no subject
Date: 2011-01-05 08:34 pm (UTC)(no subject)
From:no subject
Date: 2011-01-05 09:36 pm (UTC)Затем начнем повторять A = X[A]
Рано или поздно мы зациклимся. Но не сразу - поскольку ни один элемент не может содержать N и указывать на ячейку X[N], наша исходная точка не может принадлежать циклу. "Началом" цикла будет некая ячейка X[M], на которую есть сразу две ссылки - одна на начальной части пути (не принадлежащей циклу), вторая - уже в самом этом цикле. То есть M лежит в двух разных ячейках - это и есть искомое значение. (Сам цикл, в принципе, может быть вырожденным и состоять из единственной ячейки, ссылающейся сама на себя).
Выполним операцию "A = X[A]" N раз - это дает нам гарантию того, что A уже принадлежит циклу. Запомним это A как A0.
Начав с A0, будем выполнять "A = X[A]" (считая шаги), пока A снова не станет равным A0, - так мы определим длину цикла L (если цикл выроженный, то можно на этом и закончить).
Еще раз вернемся в начало (к последнему элементу). Сделаем L шагов указателем A, и в этот момент начнем идти (тоже от последней ячейки) еще одним указателем B. Будем двигать их синхронно - таким обазом, когда последний из них (B) войдет в цикл, первый (A) как раз закончит первый обход этого цикла. Так мы найдем начало цикла, элемент X[M].
return M;
no subject
Date: 2011-01-05 09:43 pm (UTC)Да, все правильно. Поздравляю! :)
no subject
Date: 2011-01-05 10:41 pm (UTC)class Program
{
static void Main(string[] args)
{
int[] arr = { 1, 2, 3, 4, 5, 4, 3, 2, 3, 4, 5, 6, 4, 5, 7 };
Console.Write(GetRepeatDigit(arr));
Console.ReadLine();
}
static int GetRepeatDigit(int[] arr)
{
Hashtable t = new Hashtable();
foreach (int d in arr)
{
if (t[d] != null) return d;
t[d] = 1;
}
return 0;
}
}
один проход массива
hashtable заполняется до первого найденного повторяющегося числа.
no subject
Date: 2011-01-05 11:57 pm (UTC)Итого не более 4 раз проходим по массиву и используем О(1) дополнительной памяти (два указателя).
no subject
Date: 2011-01-06 12:40 am (UTC)(no subject)
From:no subject
Date: 2011-01-06 02:51 am (UTC)(структура списка: i -> a[i], начинаем из N, чтобы не попасть в цикл сразу, идём двумя итераторами, ну Вы эту задачу наверняка знаете)
Расскажите, плз, скрытым комментарием, как делать за O(N log N) и O(1) памяти? Ничего естественного в голову не приходит :)
no subject
Date: 2011-01-06 08:34 am (UTC)http://garconnumeroun.livejournal.com/128464.html
no subject
Date: 2011-01-06 10:06 am (UTC)no subject
Date: 2011-01-06 02:20 pm (UTC)stavlju ego na mesto k, beru to cheslo kotoroe bylo na meste k dopustem j
i stavlju ego na mesto j, i tak dalee
esle na meste k_j uzhe sidid k_j znachit konec poiska
no subject
Date: 2011-01-07 02:52 pm (UTC)