Поиск двух массивов для совпадений, без дополнительной памяти
У меня было интервью на днях с Amazon, и вопрос, который они задавали мне, касался следующей проблемы.
Для двух целых массивов, содержащих любое количество элементов, как положительных, так и отрицательных, найдите числа, которые появляются в обоих массивах.
Мне удалось легко решить эту проблему с помощью HashMaps
, поэтому у него будет O(n)
вычислительная сложность, но, к сожалению, это также будет иметь сложность пространства O(n)
. Это можно сделать без дополнительной памяти путем повторения всех элементов в каждом массиве, но это будет O(n^2)
.
Интервьюер после того, как я закончил объяснение метода HashMap
, спросил, могу ли я думать о методе, который был бы O (n), вычисляемым, но не использовал бы лишнюю память. Я не мог думать ни о чем на лету, и не смог найти решение для этого. Есть ли способ найти эти значения без использования дополнительной памяти в линейном времени?
Примечание. Я задал этот вопрос в CareerCup, но все, похоже, не поняли, что мне нужно, чтобы он не использовал лишнее пространство и что он должен быть O(n)
вычислительным.
Вот код, который я использовал во время интервью. Он работает, но просто не O (1) для пробела.
import java.util.*;
public class ArrayFun {
public static void main(String[] args) {
int[] a = {1,2,3,4};
int[] b = {2,5,6,7,3,2,2,2,2,1,2,2,2,2};
ArrayList<Integer> matches = ArrayFun.findMatches(a,b);
for (int i = 0;i<matches.size();++i) {
System.out.println(matches.get(i));
}
}
public static ArrayList<Integer> findMatches(int[] a, int[] b) {
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
ArrayList<Integer> matches = new ArrayList<Integer>();
for (int i = 0;i<a.length;++i) {
map.put(a[i],0);
}
for (int i = 0;i<b.length;++i) {
if (map.get(b[i]) != null && map.get(b[i]) == 0) {
map.put(b[i],1);
matches.add(b[i]);
}
}
return matches;
}
}
Этот код вернет
1,2,3
EDIT: также, когда я не говорю о дополнительном пространстве, и O (1), я использую их взаимозаменяемо. Без дополнительного пространства я имею в виду, что небольшие переменные-заполнители хороши, но выделение новых массивов не является.
Ответы
Ответ 1
Не существует O (1) пространственного метода для нахождения пересечения двух несортированных множеств в O (n) времени.
Для типа данных с неограниченным диапазоном минимальная цена сортировки равна O (n ln n).
Для типа данных с ограниченным диапазоном сортировка по радиусу обеспечивает возможность выполнять сортировку по месту в месте в O (n ln n 'n ") времени, где n - размер данных, n' - это число значений, которые могут быть представлены, а n" связано со стоимостью проверки того, находятся ли два значения в одной группе счисления. N "временная цена может быть сброшена в обмен на стоимость O (ln n).
В специальном случае из 32-битных целых чисел n 'равно 2 ^ 32, а n "равно 1, поэтому это сработает до O (n) и обеспечит выигрышное решение для многомиллиардных наборов записей.
Для целых чисел неограниченного размера n 'и n "исключают решение времени O (n) через radix.
Ответ 2
Ключ должен сортировать два массива на месте. Я выполнил поиск "сортировки по месту на месте" и нашел In-Place Radix Sort. Я считаю, что проблема разрешима, по крайней мере, для Java int [], применяя эти идеи для сортировки каждого массива по частям, а затем делая очевидное сканирование.
Кстати, я думаю, что правильный результат для проблемы в вопросительном коде - 1, 2, 3.
Вот моя реализация, основанная на ответах на упомянутый вопрос:
public class ArrayMatch {
public static void main(String[] args) {
int[] a = { 4, 1, 2, 3, 4 };
int[] b = { 2, 5, 6, 7, 3, 2, 2, 2, 2, 1, 2, 2, 2, 2 };
System.out.print("Original problem");
printMatches(a, b);
System.out.println();
int[] a1 = { 4, 1, -1234, 2, 3, 4, Integer.MIN_VALUE };
int[] b1 = { -1234, 2, 5, 6, 7, 3, 2, 2, 2, 2, 1, 2, 2, 2, 2 , Integer.MIN_VALUE, Integer.MAX_VALUE};
System.out.print("With negatives");
printMatches(a1, b1);
System.out.println();
}
// Print all matching elements between the two arrays.
private static void printMatches(int[] a, int[] b) {
if (a.length == 0 || b.length == 0) {
return;
}
sort(a);
sort(b);
int i = 0;
int j = 0;
while (true) {
while (a[i] < b[j]) {
i++;
if (i == a.length) {
return;
}
}
while (a[i] > b[j]) {
j++;
if (j == b.length) {
return;
}
}
if (a[i] == b[j]) {
System.out.print(" " + a[i]);
do {
i++;
} while (i < a.length && a[i - 1] == a[i]);
do {
j++;
} while (j < b.length && b[j - 1] == b[j]);
}
if (i == a.length || j == b.length) {
return;
}
}
}
// In place radix sort.
private static void sort(int[] in) {
// Flip the sign bit to regularize the sort order
flipBit(in, 31);
sort(in, 0, in.length, 31);
// Flip back the sign bit back to restore 2 complement
flipBit(in, 31);
}
/**
* Sort a subarray, elements start through end-1 of in, according to the
* values in firstBit through 0.
*
* @param in
* @param start
* @param end
* @param firstBit
*/
private static void sort(int[] in, int start, int end, int firstBit) {
if (start == end) {
return;
}
int mask = 1 << firstBit;
int zeroCount = 0;
for (int i = start; i < end; i++) {
if ((in[i] & mask) == 0) {
zeroCount++;
}
}
int elements = end - start;
int nextZeroIndex = start;
int nextOneIndex = start + zeroCount;
int split = nextOneIndex;
if (zeroCount > 0 && zeroCount < elements) {
while (nextZeroIndex < split) {
if ((in[nextZeroIndex] & mask) != 0) {
// Found a one bit in the zero area, look for its partner in the one
// area
while ((in[nextOneIndex] & mask) != 0) {
nextOneIndex++;
}
int temp = in[nextZeroIndex];
in[nextZeroIndex] = in[nextOneIndex];
in[nextOneIndex] = temp;
nextOneIndex++;
}
nextZeroIndex++;
}
}
if (firstBit > 0) {
sort(in, start, split, firstBit - 1);
sort(in, split, end, firstBit - 1);
}
}
private static void flipBit(int[] in, int bitNo) {
int mask = 1 << bitNo;
for (int i = 0; i < in.length; i++) {
in[i] ^= mask;
}
}
}
Ответ 3
Один из возможных ответов аналогичен решению HashMap
... IF, вы знаете, что целые числа находятся в очень маленьком окне. Это было бы похоже на следующее: http://en.wikipedia.org/wiki/Bucket_sort
В принципе, если целые числа гарантированно находятся в определенном окне с постоянным размером (т.е. все они равны 1-1000), вы можете сделать это в постоянном пространстве, увеличивая каждую ячейку индекса = независимо от вашего номера. Это точно так же, как решение HashMap
, за исключением того, что вам не нужно учитывать все возможные целые числа, такие как HashMap
can, что позволяет сэкономить место. Если это неясно, дайте мне знать в комментариях, и я объясню далее.
Ответ 4
Я считаю, что это возможно сделать на месте с O(1)
дополнительным пространством. Я использую дополнительное предположение о том, что элементы в массивах изменяемы, а также с возможностью замены, но я считаю, что при тщательном учете предположение о мутировании может быть удалено для этой конкретной проблемы.
Основная идея - сделать хэширование на месте. Хеширование на месте может быть реализовано путем разбиения массива вокруг подходящего процентиля, например, на 90-е, используя алгоритм выбора O(n)
медианы медианов. Это делит массив на небольшую часть (около 10%) и большую часть (около 90%), элементы которой отличаются друг от друга (меньше, чем элемент раздела или нет). Вы можете затем хэш из 10% -ной части в 90% -ую часть путем замены. Это хеширование может использоваться для обнаружения дубликатов. Это O(n)
для каждой обработки 10% массива, поэтому сделано 10 раз все еще O(n)
. Я описал это гораздо более подробно, хотя с некоторым размахиванием руками я хотел бы исправить один день, в этот связанный вопрос..
Для этой конкретной проблемы вам нужно сделать 3-х разное на месте. Сначала на каждом отдельном массиве удалять дубликаты. Затем на обертке, представляющей объединенные массивы (если индекс меньше длины массива 1, индекс в массив 1, иначе индексируйте в массив 2), чтобы сообщать о дубликатах.