Нам дан отсортированный массив чисел и некоторое число K
.
Необходимо найти два числа из массива, в сумме дающих число K
. Если таких чисел нет, то вернуть пустой массив.
Если таких чисел несколько, то вернуть любую пару чисел, удовлетворяющих условию задачи.
Использовать дважды одно и то же число нельзя!
[-1, 2, 5, 8], K = 7
Ответ: [-1, 8]
[2, 4, 5], K = 8
Ответ: []
[-2, -1, 1, 2], K = 0
Ответ: [-2, 2]
Решение в лоб, простым перебором пар.
Будем суммировать пары чисел по порядку до первой пары, которая выполняет условие задачи. Если дошли до конца массива - значит нет нужных нам пар и возвращаем пустой массив.
public static int[] allPairsEnumeration(int[] arr, int k) {
for (int i = 0; i < arr.length; i++) {
var item = arr[i];
for (int j = i + 1; j < arr.length; j++) {
var item2 = arr[j];
if (item + item2 == k) {
return new int[] {item, item2};
}
}
}
return new int[] {};
}
Время работы: O(N^2)
Память: O(1)
Предыдущее решение было O(N^2), поэтому попробуем улучшить наш алгоритм.
Объявим кеш из которого мы сможем достать за O(1) необходимый нам элемент. Для данной задачи отлично подойдет HashSet
.
Заметим, что мы можем оперировать нашим искомым числом k
.
Возьмем первое число в массиве и вычтем его из k
, таким образом получим то число, которое мы должны найти в пару.
Проверим, есть ли такое число у нас в кеше (проверка у нас быстрая). Если такого числа нет, то поместим текущее число в кеш. Теперь, если это число сможет стать парой для другого мы сможем быстро найти его в кеше, а не перебирать весь массив снова.
Например, у нас есть массив [2, 3, 5] и ищем мы 7.
Создаем кеш (пока он пустой).
Берем 2 и вычитаем из искомого это число, получаем 5-ку. Ищем 5-ку в кеше. Пока в кеше у нас нет элементов и 5-ку мы не находим. Кладем 2-ку в кеш, чтобы при необходимости достать ее за O(1).
Берем 3-ку и ищем в кеше для нее 4-ку, как подходящую пару. Не находим, поэтому кладем в кеш 3-ку.
Берем 5-ку и ищем в кеше 2-ку, успешно ее находим (она у нас с первой итерации в кеше) и возвращаем нашу пару.
public static int[] withSet(int[] arr, int k) {
var cache = new HashSet<Integer>();
for (int item : arr) {
var num = k - item;
if (cache.contains(num)) {
return new int[]{item, num};
} else {
cache.add(item);
}
}
return new int[] {};
}
Время работы: O(N)
Память: O(N)
Вспомним, что по условию задачи массив отсортирован.
Возьмем число в массиве и вычтем его из k
, таким образом получим то число, которое мы должны найти в пару.
Попробуем найти искомое с помощью бинарного поиска, ведь массив у нас отсортирован по условиям задачи.
public static int[] binarySearch(int[] arr, int k) {
for (int i = 0; i < arr.length; i++) {
var num = k - arr[i];
int left = i + 1;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (num == arr[mid]) {
return new int[]{arr[i], num};
} else if (num > arr[mid]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return new int[] {};
}
Бинарный поиск работает за O(log2(N)), так как каждый раз мы делим искомый массив напополам. Поиск мы применяем в цикле, т.е. в худшем случае мы воспользуемся им N раз.
Время работы: O(N * log2(N))
Память: O(1)
Заведем два указателя на начало и конец массива. Первый в начале указывает на самое первое число, оно же самое маленькое в массиве, так как массив отсортирован. Второй указывает на самое большое число, последнее в массиве.
Сложим оба числа и сравним с искомым k
.
- Если сумма равна, то это те числа, что мы ищем
- Если сумма меньше, то мы можем сдвинуть левый указатель вправо, так как даже в сумме с максимальным числом в массиве результат оказался меньше искомого
- Если сумма больше, то мы можем сдвинуть правый указатель влево
Сделаем это в цикле и найдем искомую пару!
Грубо говоря, создадим окно в котором ищем пару. Двигаем окно и смотрим если сумма меньше, двигаем правую часть, если сумма больше двигаем левую часть.
public static int[] twoPointers(int[] arr, int k) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
if (arr[left] + arr[right] == k) {
return new int[]{arr[left], arr[right]};
} else if (arr[left] + arr[right] < k) {
left++;
} else {
right--;
}
}
return new int[]{};
}
Время работы: O(N)
Память: O(1)