А вот задачка на выходные! Она плохо подходит, чтобы спрашивать на собеседовании, потому что слишком уж на инсайт (пожалуйста, никогда не задавайте такие на собеседованиях), и слишком простая для соревнований. Как раз чтобы доставить тот самый satisfying click в мозгу, за который мы любим программирование. Итак:
Не забывайте использовать тег <spoiler> для решений в комментариях!
тривиальное решение: заводим нулевой битовый массив на 4Г битов (константная память!). если мы видим какое-то число то делаем на его позиции исключающее или с единицей. в конце только два бита будут равны одному — это и есть те числа что мы искали. в один проход по дополнительному массиву можно их найти.
Не троллить :) Давайте, чтобы не было разночтений — памяти у вас четыре килобайта.
В комментариях тоже много решений, с кодом и без, вот некоторые (список не претендует на полноту):
Решение от kmu1990
Решение от Ogra
Решение от ZyXI
Как решали бы задачу в стартапе от Demogor
Развитие задачи от deniskreshikhin
Disclaimer: пост написан на основе изрядно отредактированных логов чата closedcircles.com, отсюда и стиль изложения, и наличие уточняющих вопросов.
Есть большой массив из N 32-битных чисел. Каждое число встречается два раза, а два числа -- по одному. Найти эти два числа за один проход по массиву с константными затратами памяти (то есть не зависящими от размера массива).
Не забывайте использовать тег <spoiler> для решений в комментариях!
тривиальное решение: заводим нулевой битовый массив на 4Г битов (константная память!). если мы видим какое-то число то делаем на его позиции исключающее или с единицей. в конце только два бита будут равны одному — это и есть те числа что мы искали. в один проход по дополнительному массиву можно их найти.
Не троллить :) Давайте, чтобы не было разночтений — памяти у вас четыре килобайта.
Одно из решений
Как легко заметить, парные числа намекают, что задача про xor!
Пусть искомые числа — X и Y. Если просто сделать xor всех чисел в массиве, понятно что результатом будет X^Y, что хорошо тем, что все другие числа сократились, но плохо тем, что возможности их вычислить не дает. Что делать?
Заметим, что если бы мы заранее знали в каком бите числа X и Y отличаются (а такой бит быть обязан), то задача бы решалась просто — давайте сделаем xor всех чисел, у которых в бите N скажем единица. Все парные числа сократятся, и результатом будет X или Y. А если у нас есть еще и значение X^Y, то и второе число просто вычислить.
Более того, если у нас есть X^Y это еще и говорит, в каком бите X и Y отличаются — там где в xor 1.
Осталось понять как это сделать за один проход.
Давайте во время прохода насчитывать xor всех чисел и 32 аккумулятора. В i-ом аккумуляторе мы будем накапливать xor всех чисел, у которых 1 в i-м бите. В конце воспользуемся одним из аккумуляторов, где биты различаются.
Пусть искомые числа — X и Y. Если просто сделать xor всех чисел в массиве, понятно что результатом будет X^Y, что хорошо тем, что все другие числа сократились, но плохо тем, что возможности их вычислить не дает. Что делать?
Заметим, что если бы мы заранее знали в каком бите числа X и Y отличаются (а такой бит быть обязан), то задача бы решалась просто — давайте сделаем xor всех чисел, у которых в бите N скажем единица. Все парные числа сократятся, и результатом будет X или Y. А если у нас есть еще и значение X^Y, то и второе число просто вычислить.
Более того, если у нас есть X^Y это еще и говорит, в каком бите X и Y отличаются — там где в xor 1.
Осталось понять как это сделать за один проход.
Давайте во время прохода насчитывать xor всех чисел и 32 аккумулятора. В i-ом аккумуляторе мы будем накапливать xor всех чисел, у которых 1 в i-м бите. В конце воспользуемся одним из аккумуляторов, где биты различаются.
В комментариях тоже много решений, с кодом и без, вот некоторые (список не претендует на полноту):
Решение от kmu1990
Решение от Ogra
Решение от ZyXI
Как решали бы задачу в стартапе от Demogor
Развитие задачи от deniskreshikhin
Disclaimer: пост написан на основе изрядно отредактированных логов чата closedcircles.com, отсюда и стиль изложения, и наличие уточняющих вопросов.
rPman
цикл в (размер массива/2) итераций
{
текущее количество = 0
для каждого элемента массива
{
ищем количество чисел равных текущему минимуму (если элемент равен текущему минимуму то увеличиваем количество)
ищем следующий минимум (число больше текущего минимума и меньше или равно текущему элементу)
}
если количество чисел было больше 1 то выводим текущий минимум
}
rPman
Черт, ошибку нашел у себя:
размер внешнего цикла нужно исправить на деление с округлением в большую сторону +1 или просто тупо +2, иначе для случая когда искомые числа максимальные для массива — они не будут найдены)
либо цикл бесконечный и выходим, когда не сможем найти следующий минимум (но тогда будет доп условие)
mgrigorov_ua
Такие задачи у меня всегда вызывают чувство ущербности…
И вроде программистом себя считаешь и вроде на работу ходишь каждый день, а всё равно, никуда не заглядывая, решить не можешь.
Да чего там… подглядывая в спойлеры, мало чего понял :(
Жду решения с детальным разжевыванием!
Спасибо.
sim0nsays
Ну, в реальной работе (к сожалению) такое нужно крайне немногим и крайне редко. Для меня это скорее радостная возможность вспомнить молодость и размять мозги.
deniskreshikhin
Я бы даже сказал такие решения крайне вредные.
Все-таки уж лучше там будет подсчет с помощью map, зато каждый программист сразу поймет что там делается.
xor-магию довольно трудо разбирать и поддерживать.
gecube
А ее и ненужно разбирать. Код на XOR реально эффективный будет, а map — медленный отстой (в этом случае). Читаемость заменяется комментариями, а код обрамляется в функцию (типа черный ящик).
deniskreshikhin
Ага, покрасить и выбросить -)
sim0nsays
Ну, шанс что выпадет именно такая задача вообще бесконечно мал, слишком уж искусственно. А вот шанс использовать битовую магию, про которую задача — почему нет.
Прием с xor указателей в списке вполне рабочий, к примеру.
Разумеется, если память и/или производительность неважна — лучше писать проще.
Demogor
Если повезет — то дубляжи будут попадаться достаточно часто и размер объекта r будет скакать в пределах 4к. Превысило? Ну что ж, не повезло.
sim0nsays
ыыыыыыыы! И надеемся, что у первых кастомеров объемы маленькие, а там глядишь до следующего раунда инвестиций дотянем!
Demogor
Нормальная русская рулетка: отвалится/не отвалится в момент сдачи.
govnokoder
Искомые числа: X1 и X2
Кол-во элементов: CA
Считаем сумму всех чисел (SA), XOR всех чисел (XA)
Получаем: SA = 2a + 2b + 2c +… + X1 +… + X2 +…
XA = X1 ^ X2
Перебираем первое число от 0 до 0xfffffff так, чтобы выполнялось равенство: (SA + X1 + (X1 ^ XA)) / CA — целое
sim0nsays
ааааа, термояд! Пишите под спойлером плз.
1 1 и 1 0
Demogor
Если я не ошибаюсь, это будет не 1 проход, а 3(сумма, xor, перебор)?
ZyXI
Код на Python:
PS: комментарии пока не читал.
sim0nsays
Все так!
ZyXI
vasiatka
оставю тут. сложность O(n). память O(1). guildalfa.ru/alsha/node/29
savinov_ao
Для всех элементов i=1..N
Затем для x=0..232-1 вычисляем y = xy ^ x и проверяем что xor массивов Mods(x) и Mods(y) совпадает с d. Если совпадает, выходим по break.
Наверное так.
ankh1989
Можно например сначала сложить все числа в конечном поле, затем сложить квадраты этих чисел. В итоге пары сократятся и получится два уравнения: сумма двух числе и сумма их квадратов. Уравнения эти сводятся к выичслению квадратного корня в конечном поле, что можно сделать алгоритмом Шенкса. Проще конечно xor-ом, но если хочется загрузить того кто собеседование проводит, то конечные поля Галуа — самое то.
sim0nsays
ооооо! В конченом поле — это как? По модулю? И почему пары сократятся?
halyavin
Квадраты бесполезны, поскольку x2+y2=(x+y)2 — мы не получаем новой информации. Нужно
sim0nsays
Так поясните, что подразумевается под сложениями и умножениями, и как сокращаются пары других чисел?
halyavin
Сложение — xor. Умножение выполняется сложнее. Вначале нужно выполнить битовое умножение без переносов, а потом посчитать CRC. Еще материал по этой теме: https://habrahabr.ru/post/212095/
ankh1989
Практически это делается так:
shl = x => x = x << 1 ^ (x < 0? 0x8d: 0); // x * 2 + 2**32 + 0x8d
mul = (a, b) => b && ((b & 1? a: 0) ^ shl(mul(a, b >>> 1)));
А потом применяем вот так:
mul(0x6789, 0x9876);
Zealint
Mrrl
Зависит — примерно как 3*N бит. А решение со счётчиками — примерно N^2 бит. Конечно, ограничения задачи становятся слегка другими, но O(N)<O(N^2).
Zealint
При чём тут O(N^2)? В решении с 32-мя счётчиками будет один проход по циклу. Имеем те же O(N) битовых операций и O(1) памяти, в виде 32-х чисел размером 32 бита. Ну, плюс пара служебных переменных.
Mrrl
А в случае 16384-битных слов? Всё равно 32 счётчика?
Правда, я не уверен, что решение с x^3 вообще работает.
Zealint
Я же напомнил выше, что в задаче чётко указан размер слова — 32 бита. В случае K-битных слов по любому решение будет зависеть от K, хотя бы потому, что нужно как-то это слово считать в память, тут не важно, делаем мы xor или сумму в конечных полях. И всё равно я не вижу тут O(N^2).
ZyXI
Судя по сообщению Mrrl под
N
имел ввиду разрядность числа, а не то, что указано в условии. Т.е. если считать разрядность числа в задаче переменной и равной b, то будет O(Nb) битовых операций и O(b?) памяти в случае с b счётчиками и O(b) памяти для его предложения.Чтобы не возникало недопонимания не нужно переназначать переменные из условия.
Zealint
Даже если так, то это уже обсуждение другой задачи. А в этой задаче размер слова чётко обозначен, и это с практической точки зрения даёт надежду на вполне эффективный алгоритм. Менять условия или пытаться решить более общую задачу вместо исходной — это не всегда хорошая идея. А в случае жёстких требований на время работы даже плохая. Не вижу также смысла пытаться ставить в качестве аргумента теоретическое время работы через символ "O" (который обозначает время для b->oo), которое никакого отношения к реальности обычно не имеет, особенно при малых значениях этого b.
halyavin
Очевидно, что подразумевается, что размер чисел равен машинному слову. Я не вижу другого разумного способа формализовать задачу. В стандартной модели вычислений, операции над машинными словами выполняются за O(1) и одно машинное слово занимает O(1) памяти.
Zealint
Ну так и я тоже не вижу никаких теоретических отличий между Вашими переменными "x" и "y" в конечном поле (по какому-то модулю, явно зависящему от размера слова), и набором переменных из решения с xor, размер которых будет в точности совпадать с размером слова, потому что размер слова не стремится к бесконечности и потому что в условиях задачи (некорректно поставленной) указано, что память не должна зависеть от размера массива, а будет ли она квадратичной или линейной по числу бит в слове, не важно. Что же касается практической эффективности обоих решений, то это тоже большой вопрос, что будет быстрее — 32 xor'a или вычисления кубов по модулю. Хотя я, конечно, не буду спорить о том, что любые правильные методы сами по себе заслуживают внимания и каждый из них по-своему хорош.
halyavin
Чтобы N стремился к бесконечности, битность чисел тоже должна стремиться к бесконечности. Даже в варианте где разрешены повторения парных чисел, очень странно указать 32 бита и не подразумевать, что числа имеют размер машинного слова. Тогда уж надо указывать 30 бит или 50 бит (компьютеры давно уже 64-битные, а это самое круглое число меньше 64).
Некорректно поставленная задача означает лишь то, что ее нужно преобразовать в корректно поставленную наиболее естественным способом, используя общепринятые умолчания. После чего решить. При объяснении решения, в зависимости от обратной реакции вопрошающего, возможно преобразовать решение обратно. Подход требующий 32 слова в памяти не является решением получившейся корректно поставленной задачи, значит это не правильное решение. Если бы оно подразумевалось, нужно было бы писать в условии не O(1) памяти, а O(32) памяти. Ну или сразу писать корректную формулировку с переменной длиной машинного слова.
PS Конечное поле тут не по модулю простого числа. Поле должно иметь характеристику 2, чтобы парные числа сокращались.
astowork
Задачке лет 5… это классика…
Скрытый текстtype
TMyElement= integer;
PMyElement= ^TMyElement;
TMyArray= array of TMyElement;
TBitInfo= record
Mask: TMyElement;
Sum: TMyElement;
end;
const
BitCount = SizeOf(TMyElement)*8;
LastBitNo= BitCount-1;
//поиск 0..2 непарных среди Count элементов массива, начиная с p
procedure Find012(p: PMyElement; Count: integer; var Res: TMyArray);
var
bi: array[0..LastBitNo] of TBitInfo;
t: TMyElement;
q: PMyElement;
i: integer;
begin;
if (p=nil) or (CountLastBitNo;
end;
end;
end;
end;
pitrpg
Вроде рабочий вариант решения, но с некоторыми ограничениями jsfiddle.net/669a3L45
psplus2016
Элементарно.
Int main()
{
vector v = {1,2,3,3,1,2,4,7};
//vector v = {1,2,7,4,1,2,3,3};
//vector v = {1,2,3,4,1,2,3,7};
int p = 0;
int s = 0;
int pp = 0;
int ss = 0;
for( size_t i = 0; i < v.size(); i++ )
{
if( i < (v.size() / 2) )
p = p ^ v[i];
else
s = s ^ v[i];
if( (i%2) == 0 )
pp = pp ^ v[i];
else
ss = ss ^ v[i];
}
if( (p^pp) == p || (s^ss) == s )
{
cout
psplus2016
нифига не работает :) неверная тестовая последовательность была — привела к ложному "верному" результату.
venco
Моя попытка#include
int main()
{
unsigned bit[32] = {}, all = 0, v, i;
while ( std::cin >> v ) {
all ^= v;
for ( i = 0; i < 32; ++i ) {
if ( v&(1
venco
Мда, тэги не прошли. И редактировать не могу.
В общем, посмотрел другие решения, и эта идея уже была приведена несколько раз. Памяти требуется на 32-е xor суммы.
ZyXI
На 33. Плюс ещё куча вещей вроде памяти под исполняемый код и временные переменные вроде i и v, которые есть, но на которые всем (включая меня) при подсчёте памяти было наплевать.
venco
Ок, памяти требуется O(B^2) бит, где B-размер чисел в битах, остальное — меньшего порядка.
А ещё общий xor можно собрать из побитовых xor-ов по диагонали.
nikitadanilov
Проще, наверное, показать код, чем описать:
nikitadanilov
Update: все-таки опишу.
Для каждого номера бита i, от 0 до 31, найдем сумму всех элементов массива, у которых i-й бит установлен (s[i][1]) и сумму элементов, у которых i-й бит не установлен (s[i][0]), все суммы по модулю 2. Всякий парный элемент участвует точно в тех же суммах, что и его пара и, таким образом, общий вклад от пары во всякой сумме равен 0.
Предположим, сначала, что непарные элементы A и В отличны от нуля. Тогда отличны от нуля только те суммы s[i][j], в которых участвует один или оба непарных элемента. Но непарные элементы отличаются хотя бы одним битом (иначе они были бы парой) k, тогда один из них равен s[k][0], а другой — s[k][1].
Если же один из непарных элементов равен нулю, то для любого k, s[k][0] и s[k][1] и дают искомые числа.