Intersection of Two Arrays
Тебе даны два массива целых чисел: nums1 и nums2. Верни все значения, которые встречаются в обоих массивах, в порядке возрастания. Каждое общее значение должно встречаться в ответе один раз, независимо от того, сколько раз оно повторяется в каждом из массивов.
Функция
- nums1integer-array
- первый список целых чисел
- nums2integer-array
- второй список целых чисел
- Возвращаетinteger-array
- значения, присутствующие в обоих списках, каждое по одному разу, в порядке возрастания
Ограничения
1 ≤ nums1.length, nums2.length ≤ 5000-105 ≤ nums1[i], nums2[i] ≤ 105- Хотя бы одно значение присутствует в обоих массивах.
Примеры
- Ввод
- nums1 = [6, 2, 9, 2, 4]nums2 = [4, 4, 1, 6]
- Вывод
- [4, 6]
- Пояснение
4и6есть в обоих массивах.4встречается вnums2дважды, но указан один раз, а2и9никогда не встречаются вnums2.
- Ввод
- nums1 = [-3, 0, 7]nums2 = [7, -3, -3, 5]
- Вывод
- [-3, 7]
- Пояснение
-3и7есть в обоих массивах. В порядке возрастания сначала идет-3, хотя вnums2первым идет7.
+16 скрытых тестов при отправке
Дополнительный вопрос
Что, если в nums1 содержится 10 значений, а в nums2 — миллион, уже отсортированных? Какой подход ты бы выбрал и может ли бинарный поиск быть эффективнее полного прохода?
Подсказки
Открывайте по одной. Каждая подсказывает чуть больше.
Для каждого значения
nums1можно просканировать весь массивnums2. При 5000 значениях в каждом массиве это до2.5 × 10^7сравнений. Какой вопрос ты задаёшь снова и снова?Повторяющийся вопрос: «есть ли это значение в другом массиве?». Хеш-множество, созданное на основе одного массива, отвечает на него в среднем за постоянное время.
Создай множество из
nums1. Пройдись поnums2; если значение есть во множестве, добавь его в ответ и удали из множества, чтобы его повторная копия не могла быть добавлена позже. Отсортируй ответ перед тем, как вернуть его.
Решение
Эту задачу определяют два момента: значение, которое повторяется в обоих массивах, всё равно входит в ответ только один раз, а ответ должен быть отсортирован. Сравнение каждой пары работает, но требует n × m сравнений — 2.5 × 10^7, если в обоих массивах по 5000 значений. Сортировка обоих массивов позволяет двум указателям находить общие значения по порядку, а хеш-множество для одного массива позволяет за постоянное время ответить на вопрос «есть ли это значение в nums1?».
Сравните каждую пару
Верно, но не успевает на самых больших тестах
Идея
Возьмите каждое значение из nums1 и ищите его в nums2. Остановите поиск при первом совпадении и пропустите значение, которое уже есть в ответе, поэтому для [8, 8, 8, 8] и [8, 8] результатом будет одна 8, а не четыре. В конце отсортируйте ответ.
Это правильно, потому что значение попадает в ответ, только если какая-либо его копия в nums1 находит совпадение в nums2, а пропуск не позволяет добавить его дважды.
Это медленно, потому что для каждого значения из nums1 может выполняться поиск по всему nums2. Если в каждом массиве по 5000 значений, это до 2.5 × 10^7 сравнений, а в больших тестах большинство значений не находит совпадений, поэтому большинство поисков доходит до конца.
Алгоритм
- Начни с пустого списка ответов.
- Для каждого значения
aвnums1пропусти его, если оно уже есть в ответе. - В противном случае просматривай
nums2; при первом значении, равномa, добавьaв ответ и прекрати просмотр. - Отсортируй ответ по возрастанию и верни его.
def intersection(nums1, nums2):
result = []
for a in nums1:
if a in result:
continue
# Look for a anywhere in nums2.
for b in nums2:
if a == b:
result.append(a)
break
result.sort()
return resultОтсортируй оба, затем пройдись двумя указателями
Идея
После сортировки пример 1 превращается в [2, 2, 4, 6, 9] и [1, 4, 4, 6]. Поставьте указатель i в начало первого массива, а j — в начало второго. Указатель на меньшее значение сдвигается вперёд: это значение не может совпасть ни с одним из дальнейших значений другого массива, где все значения не меньше него. Когда оба указателя указывают на одно и то же значение, оно есть в обоих массивах, поэтому добавьте его и сдвиньте оба указателя.
Рассмотрим пример: 2 > 1 сдвигает j, оба значения 2 меньше 4, поэтому сдвигается i, 4 = 4 — добавляем 4, второе значение 4 меньше 6, поэтому сдвигается j, а 6 = 6 — добавляем 6. Значение, встречающееся несколько раз в обоих массивах, например 2 в [2, 2, 3] и [2, 2], совпадает больше одного раза; сравнение с последним добавленным значением позволяет оставить только одну копию. Результат сразу получается отсортированным, без дополнительных шагов.
Сортировка требует O(n log n + m log m), а проход — O(n + m), поскольку на каждом шаге сдвигается как минимум один указатель. В большинстве вариантов сортируются копии массивов, что требует O(n + m) дополнительной памяти. Если входные данные можно переупорядочить, отсортируйте массивы на месте, как это делает код на C, — тогда единственная дополнительная память понадобится для ответа.
Алгоритм
- Отсортируй оба массива.
- Установи
i = 0иj = 0. - Пока оба указателя находятся внутри своих массивов, перемещай указатель, указывающий на меньшее значение.
- При равных значениях добавь это значение, если оно не равно последнему добавленному значению, затем перемести оба указателя.
- Верни ответ.
def intersection(nums1, nums2):
a = sorted(nums1)
b = sorted(nums2)
i, j = 0, 0
result = []
while i < len(a) and j < len(b):
if a[i] < b[j]:
i += 1
elif a[i] > b[j]:
j += 1
else:
# A shared value: keep it once, even if it repeats.
if not result or result[-1] != a[i]:
result.append(a[i])
i += 1
j += 1
return resultХеш-множество первого массива
Идея
Помести каждое значение из nums1 в хеш-множество. В примере 1 множество выглядит так: {6, 2, 9, 4}: повторяющаяся 2 схлопывается при добавлении. Затем пройди по nums2 и проверь каждое значение в множестве за константное время. Первая 4 там есть, поэтому она попадает в ответ. Вторая 4 попасть туда не должна, поэтому удаляй значение из множества сразу после совпадения. 1 там нет, а 6 есть, что дает [4, 6].
Удаление при совпадении позволяет сохранить каждое значение только один раз: после первого совпадения значение исчезает из множества, поэтому более поздние копии в nums2 ничего не находят. Каждое добавленное значение есть в обоих массивах, и каждое общее значение добавляется при появлении его первой копии в nums2.
Построение множества и проход занимают в среднем O(n + m). Ответ получается в порядке элементов nums2, поэтому в конце отсортируй его; в нем содержится k ≤ min(n, m) значений, что требует O(k log k). В C нет встроенного множества, поэтому в коде на C используется массив флагов с индексами value + 10^5, что работает, поскольку значения ограничены.
Алгоритм
- Создай хеш-множество
firstизnums1. - Для каждого значения в
nums2, если оно есть вfirst, добавь его в ответ и удали его изfirst. - Отсортируй ответ по возрастанию.
- Верни его.
def intersection(nums1, nums2):
first = set(nums1)
result = []
for num in nums2:
if num in first:
result.append(num)
# Remove it so a repeat in nums2 is not added twice.
first.remove(num)
result.sort()
return result
Ловушки и крайние случаи
Большинство неправильных ответов здесь связано с повторяющимися значениями и порядком вывода.
- Добавление значения каждый раз, когда оно совпадает.
[2, 2, 3, 3, 3]и[3, 2, 2]имеют два общих значения, поэтому ответ —[2, 3], а не[3, 2, 2]. - Возврат значений в порядке их обнаружения. Обход хеш-множества следует за
nums2, поэтому[7, -3]всё равно нужно отсортировать, получив[-3, 7]. - Сортировка чисел как текста. В JavaScript
sort()без компаратора сравнивает строки, поэтому[100000, 99]остаётся в таком порядке. Передайте(x, y) => x - y. - Использование пересечения множеств и забытый порядок.
set(nums1) & set(nums2)в Python находит нужные значения в произвольном порядке; оберните результат вsorted. - Индексация массива флагов исходным значением.
-3не является допустимым индексом; сначала сдвиньте каждое значение на10^5.
Частые вопросы4
Какова временная сложность пересечения двух массивов?
С хеш-множеством поиск общих значений в среднем занимает O(n + m), а сортировка k значений ответа добавляет O(k log k); множество использует O(n) памяти. Сортировка обоих массивов и проход по ним с помощью двух указателей занимает O(n log n + m log m). Сравнение каждой пары занимает O(n × m).
Что использовать: хеш-множество или два указателя?
Используй хеш-множество, если массивы не отсортированы и доступна память: так потребуется меньше всего работы. Используй два указателя, если оба массива уже отсортированы или если памяти мало и их можно отсортировать на месте. При проходе не требуется множество, а ответ получается упорядоченным.
Как сохранить повторяющиеся значения при пересечении?
Если значение должно появляться столько раз, сколько оно встречается в обоих массивах, так что [3, 1, 3, 3] и [3, 3] дают [3, 3], замените множество картой подсчёта. Подсчитайте значения в nums1, а для каждого значения из nums2, количество которого больше нуля, добавьте его и уменьшите его количество. При обходе двумя указателями удалите проверку на совпадение с последним добавленным значением.
Как найти пересечение, если один массив слишком велик, чтобы поместиться в памяти?
Создайте хеш-множество из массива, который помещается в память, а большой массив читайте по частям, проверяя каждое значение по множеству и удаляя его при совпадении. Объём используемой памяти останется равным размеру меньшего массива. Если ни один из массивов не помещается в память, отсортируйте оба массива на диске и пройдите по отсортированным файлам с помощью двух указателей.
Похожие задачи
Задачи на те же идеи. Решите две или три, и приём запомнится.
Python
def intersection(nums1, nums2):
# Напишите код здесьСлучай 1
Случай 2
Ввод
nums1 = [6, 2, 9, 2, 4] nums2 = [4, 4, 1, 6]
Ожидается
[4, 6]