C/C++

Leetcode наносит ответный удар. Поиск дубликатов. Делаем из Easy Hard.

В общем-то, задача как задача. 217. Contains Duplicates. Отмечена Easy.
https://leetcode.com/problems/contains-duplicate/

Вернуть true, если список содержит дубликаты, и false, если все элементы уникальны. До 100 тысяч элементов в диапазоне 32-битного знакового целого числа.

Тут даже аудитория видеокурсов знает, что надо сделать set, запихать в него элементы и сравнить его длину с длиной массива. Только самые дремучие будут бегать по массиву двойным циклом за O(n²), впрочем, сэкономив при этом изрядно памяти.

Думал, сейчас за пару минут решу и дальше пойду. Но что-то пошло не по плану.
 class Solution {
public:
bool containsDuplicate(vector& nums) {
unordered_set nset;
nset.reserve(nums.size() / 2);
for (const auto n : nums) {
if (nset.contains(n)) return true;
nset.insert(n);
}
return false;
}
};
Я разве не зарезервировал место, предотвращая частую реаллокацию с перемещением элементов? Разве тут нет раннего выхода по первому дубликату? Что в этом коде вообще лишнее?

Вспомнился розыгрыш с какого-то радио 20-летней давности, где крутили 4 вопроса:
1) Это же hash set?
2) У него ведь линейная сложность?
3) А почему я тогда получаю такие результаты?4) А кто может ответить на этот вопрос?
И дальше - к п. 1.

Заметим, это была лучшая из 6 попыток, остальные доходили до 112ms и 65%.

И, наконец, переходим к вопросу.
Как это нужно решать, чтобы получить хотя бы вот такой результат?
Т.е. время выполнения снижено почти вдвое, а футпринт памяти - на 10 мегов.
Смотрел солюшены, люди не понимают разницы между std::set и std::unordered_set, а большинство бегает по массиву теми самыми циклами за O(n²).

Сразу скажу, что коварство литкода в тестовых данных этой "лёгкой" задачи превосходит даже их обычные традиции. Есть кейсы с очень длинными списками. Есть кейсы с данными, специально подобранными для максимизации коллизий в STL hash set, например, отличающимися на один бит в средних разрядах (так обходят внутреннее дополнительное хеширование старших разрядов в младшие). Естественно, как и во всех прочих тестах, присутствуют INT_MIN, INT_MAX, так что надеяться на ограниченность диапазона элементов не стоит.
Артем Каляшев
Артем Каляшев
87 571
Код, который первым приходит на ум
 class Solution { 
public:
bool containsDuplicate(vector& nums) {
sort(nums.begin(), nums.end());
return adjacent_find(nums.begin(), nums.end()) != nums.end();
}
};
Результат, первого же тестаСкорость, вполне приличная, а вот память как-то не очень. Но я же лишнего не брал :(
ФА
Фозилжон Атамуратов
67 105
Лучший ответ
Артем Каляшев 80ms - слабенько. И это - O(n log n) из-за сортировки. Другое дело, почему хэшмэп отрабатывает хуже...
Кстати, можно было бы попробовать выбором Хоара: это частичная сортировка с ранним выходом. В другой задаче я такое применял. Но в худшем случае - всё равно будет O(n log n), т.к. весь массив придётся отсортировать.
Артем Каляшев Но всё-таки - топовый солюшен по скорости - это своя хеш-таблица без вёдер и отключение синхронизации i/o (движок во многих тестах использует i/o, и на него эта настройка влияет, я проверял). По крайней мере, на сегодня - топовый.
https://leetcode.com/problems/contains-duplicate/submissions/1031795370/

А минимум памяти - 57.101 Мб (на алгоритме с сортировкой как раз). Меньше было бы только у квадратичного алгоритма, но не проходит по лимиту времени. Может, есть какое-то шаманство, отключение каких-то буферов рантайма... Будет время - попробую полазить по решениям других тестов.
 lass Solution { 
public:
static vector num;
bool containsDuplicate(vector& nums)
{
bool res = false;
for (auto& i : nums)
{
if (num[i + 1000000000]) {res = true; break;} else num[i + 1000000000] = true;
}
for (auto&i:nums) num[i + 1000000000] = false;
return res;
}
};
vector Solution::num = vector(2000000001);
80 мс
V.
Vidadi .......
51 417
Артем Каляшев А эта булева специализация вектора умеет пропускать диапазоны? Если добавить 2 млрд, отожрёт 2 млрд бит?
Артем Каляшев Они могут тест гонять в нескольких потоках в параллель.
Там же если тыкнуть, то выдает код, который набрал такой результат. Среди лучших - свои хеши и сортировка + проход

https://leetcode.com/problems/contains-duplicate/submissions/1031788760/
Slava Romov
Slava Romov
34 941
Артем Каляшев Сдаётся мне, что самое лучшее в таких задачах (и особенно с пакостными входными данными) - это ΑΜΤ, который оставляет далеко позади обычные хеш-таблицы. Но тут сразу же возникает вопрос: где в этой задаче Easy? Ну, естественно, наляпать абы что всегда легко, но мы ж о качественном решении говорим. И ещё - меня сильно разочаровал КоSTыLь. По ходу, он отстаёт от жизни лет на 20.
Артем Каляшев Вот эта штука сразу убирает 40ms от времени выполнения.
 auto init = []()
{ ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 'c';
}();
Ваш код выглядит вполне разумным и эффективным для проверки наличия дубликатов в списке. Однако есть несколько вещей, которые можно улучшить или уточнить:

1. `.reserve()` вызывается на контейнере `nset`, чтобы попробовать зарезервировать место для указанного количества элементов. Но здесь есть небольшая недоразумение. `.reserve()` не влияет на текущий размер контейнера, он лишь резервирует память для будущих элементов. То есть, это не сэкономит память на данный момент. Вместо этого, вы можете просто проигнорировать этот вызов.

2. Вместо `nset.contains(n)` вы можете использовать просто `nset.count(n)`. Метод `count()` вернет 1, если элемент найден, и 0, если элемент отсутствует. Это более привычное выражение для множеств.

3. Если вы хотите обеспечить ранний выход при нахождении дубликата, то можно немного модифицировать ваш цикл:
 ```cpp  

for (const auto n : nums) {

if (nset.count(n)) {

return true;

}

nset.insert(n);

}

```


4. Для обработки больших списков с диапазоном до 100 тысяч элементов, как вы описали, ваш подход с использованием хеш-множества (unordered_set) является одним из наиболее эффективных и оптимальных решений.

5. В целом, ваш код не имеет лишних или ненужных элементов. Он достаточно компактен и четко выполняет поставленную задачу.

Итак, внесение предложенных выше изменений поможет вам улучшить код, но исходный код уже является хорошим решением для задачи проверки наличия дубликатов в списке.
Артем Каляшев Хочешь совет? Никогда не пиши лажу и не тиражируй чужую лажу. Однажды придётся отвечать за слова.
Ставь блок
Бля сложна