Разберём чуть более сложную задачу. Итак, вам дана последовательность неотрицательных целых чисел $a_1, dots, a_{n}$. Вычислите
$$
maxlimits_{1 le i neq j le n}a_i cdot a_j , .
$$
Обратите внимание, что $i$ и $j$ должны быть разными, хотя в каких-то случаях можно наблюдать, что $a_i=a_j$.
- Формат ввода: Первая строка содержит целое число $n$. Следующая строка содержит $n$ неотрицательных целых чисел $a_1, dotsc, a_{n}$ (разделены пробелами).
- Формат вывода: Максимальное попарное произведение.
- Ограничения: $2 le n le 2cdot10^5$; $0 le a_1, dots, a_{n} le 2cdot 10^5$.
- Примеры
Пример 1
Ввод | Вывод |
---|---|
3 1 2 3 |
6 |
Пример 2
Ввод | Вывод |
---|---|
10 7 5 14 2 8 8 10 1 2 3 |
140 |
Наивный подход
Наивный способ решить задачу Максимальное произведение
— перебрать все возможные пары вводных элементов $A[1dotsc n]=[a_1,dotsc, a_n]$ и найти пару, которая даёт наибольшее произведение.
MaxPairwiseProductNaive(A[1..n]):
product = 0
for i from 1 to n
for j from 1 to n
if i != j
if product < A[i] * A[j]
product = A[i] * A[j]
return product
Этот код можно оптимизировать и сократить следующим образом.
MaxPairwiseProductNaive(A[1..n]):
product = 0
for i from 1 to n
for j from i+1 to n
product = max(product, A[i] * A[j])
return product
Реализуйте этот алгоритм, используя ваш любимый язык программирования. Если вы используете C++
, Java
или Python3
, вам могут пригодиться начальные заготовки (для всех задач из книги мы предлагаем стартовые заготовки с использованием этих трёх языков в интерфейсе тестирующей системы).
С другими языками вам понадобится сделать работу с нуля.
Стартовые заготовки решений для C++
, Java
и Python3
представлены ниже.
C++
#include <iostream>
#include <vector>
#include <algorithm>
int MaxPairwiseProduct(const std::vector<int>& numbers) {
int max_product = 0;
int n = numbers.size();
for (int first = 0; first < n; ++first) {
for (int second = first + 1; second < n; ++second) {
max_product = std::max(max_product,
numbers[first] * numbers[second]);
}
}
return max_product;
}
int main() {
int n;
std::cin >> n;
std::vector<int> numbers(n);
for (int i = 0; i < n; ++i) {
std::cin >> numbers[i];
}
std::cout << MaxPairwiseProduct(numbers) << "n";
return 0;
}
Java
import java.util.*;
import java.io.*;
public class MaxPairwiseProduct {
static int getMaxPairwiseProduct(int[] numbers) {
int max_product = 0;
int n = numbers.length;
for (int first = 0; first < n; ++first) {
for (int second = first + 1; second < n; ++second) {
max_product = Math.max(max_product,
numbers[first] * numbers[second]);
}
}
return max_product;
}
public static void main(String[] args) {
FastScanner scanner = new FastScanner(System.in);
int n = scanner.nextInt();
int[] numbers = new int[n];
for (int i = 0; i < n; i++) {
numbers[i] = scanner.nextInt();
}
System.out.println(getMaxPairwiseProduct(numbers));
}
static class FastScanner {
BufferedReader br;
StringTokenizer st;
FastScanner(InputStream stream) {
try {
br = new BufferedReader(new
InputStreamReader(stream));
} catch (Exception e) {
e.printStackTrace();
}
}
String next() {
while (st == null || !st.hasMoreTokens()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
}
}
Python
def max_pairwise_product(numbers):
n = len(numbers)
max_product = 0
for first in range(n):
for second in range(first + 1, n):
max_product = max(max_product,
numbers[first] * numbers[second])
return max_product
if __name__ == '__main__':
_ = int(input())
input_numbers = list(map(int, input().split()))
print(max_pairwise_product(input_numbers))
После проверки вы можете увидеть такое сообщение:
Failed case #4/17: time limit exceeded
Дело в том, что мы проверяем ваше решение на тестовых примерах — это помогает убедиться, что программа работает быстро и без ошибок.
В результате мы, как правило, знаем, какие ошибки вы допустили.
Сообщение выше говорит о том, что предложенная программа превышает ограничение по времени в 4-м тестовом примере из 17.
💡 Остановитесь и подумайте:
Почему решение не укладывается в ограничение по времени?
MaxPairwiseProductNaive
выполняет порядка $n^2$ шагов при последовательности длиной $n$.
При максимальном возможном значении $n=2cdot 10^5$ количество шагов будет порядка $4cdot 10^{10}$.
Так как большинство современных компьютеров выполняют около $10^8$–$10^9$ базовых операций в секунду (разумеется, это зависит от компьютера),
выполнение MaxPairwiseProductNaive
может занять десятки секунд.
Это превысит временное ограничение задачи.
Нам нужен более быстрый алгоритм!
Быстрый алгоритм
А что если внимательнее рассмотреть более мелкие примеры— скажем, $[5,6,2,7,4]$? Эврика! Достаточно помножить два самых больших элемента массива — $7$ и $6$.
То есть нам достаточно просканировать последовательность лишь дважды. При первом сканировании мы найдем самый большой элемент, затем — второй по величине.
MaxPairwiseProductFast(A[1..n]):
index_1 = 1
for i from 2 to n
if A[i] > A[index_1]
index_1 = i
index_2 = 1
for i from 2 to n
if A[i] != A[index_1] and A[i] > A[index_1]
index_1 = i
return A[index_1] * A[index_2]
Тестирование и отладка
Реализуйте этот алгоритм и протестируйте его на вводе $A=[1,2]$. Как и ожидалось, алгоритм выводит $2$.
Затем проверьте на вводе $A=[2,1]$. На удивление, алгоритм выводит $4$.
Изучив код, вы обнаружите, что после первого цикла $index_1=1$. Далее алгоритм инициализирует $index_2$ значением $1$, и цикл for не обновляет $index_2$.
В результате перед возвратом $index_1=index_2$. Чтобы этого избежать, необходимо изменить псевдокод следующим образом:
MaxPairwiseProductFast(A[1..n]):
index_1 = 1
for i from 2 to n
if A[i] > A[index_1]:
index_1 = i
if index_1 = 1
index_2 = 2
else:
index_2 = 1
for i from 1 to n
if A[i] != A[index_1] and A[i] > A[index_1]
index1 = i
return A[index_1] * A[index_2]
Опробуйте этот код на маленьких наборах данных $[7,4,5,6]$, чтобы убедиться, что он выдает правильные результаты. Затем попробуйте ввод.
2 100000 90000
Может оказаться, что программа выдает что-то вроде $410,065,408$ или даже отрицательное число вместо правильного результата — $9,000,000,000$.
Вероятнее всего, это вызвано целочисленным переполнением. Например, на языке C++
такое большое число, как $9,000,000,000$, не умещается в стандартный тип int
, который занимает 4 байта на большинстве компьютеров и варьируется от $-2^{31}$ до $2^{31}-1$ при
$$
2^{31}=2,147,483,648 ,.
$$
Соответственно, вместо использования типа int
в C++
при вычислении произведения и сохранении результата вам нужно использовать тип int64_t
.
Это предотвратит целочисленное переполнение, так как тип int64_t
занимает 8 байтов и хранимые значения варьируются от $-2^{63}$ до $2^{63}-1$ при
$$
2^{63}=9,223,372,036,854,775,808 , .
$$
Затем протестируйте вашу программу с большими наборами данных, например, с массивом $A[1 dotsc 2 cdot 10^5]$, где $A[i]=i$ для всех $1 le i le 2 cdot 10^5$.
Это можно сделать двумя способами:
- Создать массив в программе и передать его
MaxPairwiseProductFast
(чтобы не считывать его из стандартного ввода). - Создать отдельную программу, которая запишет такой массив в файл
dataset.txt
. Затем передать этот набор данных вашей программе из консоли:
yourprogram < dataset.txt
Убедитесь, что при обработке данных ваша программа укладывается в ограничение по времени и выдаёт верный результат: $39,999,800,000$.
Теперь вы можете быть уверенным, что ваша программа работает!
Однако система оценки снова ругается:
Failed case #5/17: wrong answer
Но как создать такой тестовый сценарий, который приведет к сбою программы и поможет понять, что с ней не так?
А в чём ошибка?
Вероятно, вас интересует, почему мы не предоставили 5-й набор данных из 17 — тот, который привел к сбою программы?
Причина проста: в реальности вам не будут показывать тестовые примеры.
Даже опытные программисты при решении задач с алгоритмами часто совершают ошибки, которые трудно обнаружить.
Поэтому важно научиться находить баги как можно раньше.
Когда авторы этой книги только начинали программировать, они ошибочно полагали, что почти все их программы правильные.
Сейчас же мы знаем, что при первом запуске наши программы почти никогда не верны.
Когда разработчик уверен в работе своей программы, он зачастую использует всего лишь несколько примеров для тестирования.
Если результаты выглядят приемлемо, он считает свою работу законченной — но это путь к катастрофе.
Если вы хотите убедиться, что ваша программа работает всегда, то советуем тщательно подобрать примеры для тестирования.
Реализация алгоритмов, а также их тестирование и отладка будут бесценным навыком для вашей будущей карьеры программиста.
Стресс-тестирование
Представляем вам стресс-тестирование — технику, которая позволяет генерировать тысячи тестовых сценариев. С её помощью можно найти тот, из-за которого провалилось ваше решение.
Стресс-тестирование состоит из четырёх частей:
- Реализация алгоритма.
- Альтернативная, банальная и медленная, но правильная реализация алгоритма для той же самой задачи.
- Генератор случайных тестов.
- Бесконечный цикл, генерирующий тесты и передающий их обоим вариантам реализации для сравнения результатов. Если результаты разнятся, выводятся оба результата и пример для тестирования, а программа останавливается. В ином случае цикл повторяется.
Стресс-тестирование основано на идее, что две правильных реализации
с каждым тестом должны приводить к одному ответу (при условии, что ответ на задачу уникален).
Однако если одна из реализаций неправильна, должен существовать такой тест, который приводит к разным ответам.
Единственный случай, при котором это не так, — когда в обеих реализациях есть одна и та же ошибка.
Но это маловероятно — если ошибка не где-то в программе ввода/вывода, общей для обоих решений.
Действительно, если одно решение правильно, а другое — нет, то существует сценарий тестирования, при котором они различаются.
Если оба решения неверны, но баги отличаются — скорее всего, есть тест, при котором два решения дают разные результаты.
Продемонстрируем стресс-тестирование MaxPairwiseProductFast
,
используя
MaxPairwiseProductNaive
в качестве тривиальной реализации:
StressTest(N, M):
while true:
n = ... // случайное целое число между 2 и N
// создать массив A[1..n]
for i from 1 to n
A[i] = ... // случайное целое число между 0 и M
print(A[1..n])
result_1 = MaxPairwiseProductNaive(A)
result_2 = MaxPairwiseProductFast(A)
if result_1 = result_2:
print("OK")
else:
print("Wrong answer:", result_1, result_2)
return
Представленный выше цикл while
сначала генерирует длину вводной последовательности $n$, случайное число между $2$ и $N$.
Оно должно быть не менее $2$: формулировка задачи гласит, что $n ge 2$.
Параметр $N$ должен быть достаточно маленьким, чтобы позволить нам рассмотреть множество тестов, несмотря на то, что наши решения медленные.
Сгенерировав $n$, мы генерируем массив $A$ с $n$ целыми числами от $0$ до $M$ и выводим его, чтобы по ходу бесконечного цикла мы всегда знали,
какой тест проходит сейчас. Это упростит нахождение ошибок в коде для генерации теста. Затем мы вызываем два алгоритма для $A$ и сравниваем результаты.
Если результаты отличаются, мы их печатаем и останавливаемся. В ином случае мы продолжаем цикл while.
Давайте запустим StressTest(10, 100'000)
и скрестим пальцы в надежде, что он выдаст Wrong answer
.
Для нас это выглядит как-то так (результат может отличаться на вашем компьютере из-за другого генератора случайных чисел).
... OK 67232 68874 69499 OK 6132 56210 45236 95361 68380 16906 80495 95298 OK 62180 1856 89047 14251 8362 34171 93584 87362 83341 8784 OK 21468 16859 82178 70496 82939 44491 OK 68165 87637 74297 2904 32873 86010 87637 66131 82858 82935 Wrong answer: 7680243769 7537658370
Ура! Мы нашли пример, в котором MaxPairwiseProductNaive
и MaxPairwiseProductFast
приводят к разным результатам, и теперь можем проверить, что именно пошло не так.
Затем мы отлаживаем это решение через этот пример, находим баг, исправляем его и повторяем стресс-тестирование.
💡 Остановитесь и подумайте:
Видите что-то подозрительное в найденном наборе данных?
Обратите внимание, что генерировать тесты автоматически и проводить стресс-тестирование легко, но находить и исправлять баги — сложно.
Прежде чем углубиться в отладку багов, давайте попробуем сгенерировать тестовый пример поменьше — это упростит нам работу.
Для этого мы поменяем $N$ с 10 на 5 и $M$ с $100,000$ на $9$.
💡 Остановитесь и подумайте:
Почему мы сначала запустили StressTest
с большими параметрами $N$ и $M$, а теперь хотим запустить с маленькими?
Затем мы заново начинаем стресс-тестирование и получаем следующее:
... 7 3 6 OK 2 9 3 1 9 2 3 Wrong answer: 81 27
Медленный алгоритм MaxPairwiseProductNaive
даёт верный ответ $81$ ( $9 cdot 9 = 81$ ), но быстрый MaxPairwiseProductFast
— неверный ($27$).
💡 Остановитесь и подумайте:
Как может выйти, что MaxPairwiseProductFast
выдаёт $27$?
Чтобы избавиться от багов в первом решении, давайте проверим, какие два числа он считает наибольшими.
Для этого мы добавим следующую строку перед return
в функции MaxPairwiseProductFast
:
print(index_1, index_2)
Когда мы снова начинаем стресс-тестирование, мы получаем следующее:
... 7 3 6 OK 2 9 3 1 9 2 3 Wrong answer: 81 27
Обратите внимание, что наши решения работали и давали сбой на одних и тех же примерах тестирования, так как мы ничего не меняли в генераторе тестов.
Он использует псевдослучайные числа при создании тестов вместо действительно случайных.
Это значит, что последовательность выглядит случайной, но она одинакова каждый раз, когда работает программа.
Такое свойство удобно и важно. Советуем вам использовать эту практику, потому что в детерминированных программах (тех, что всегда выдают одинаковый результат при одинаковых вводных данных) легче находить баги, чем в недетерминированных.
Давайте теперь рассмотрим $index_1=2$ и $index_2=3$. Если мы обратим внимание на код для определения второго максимального числа, то заметим неочевидный баг.
Когда мы использовали условие для $i$ (число не должно быть таким же, как предыдущее самое большое), вместо сравнения $i$ и $index_1$ мы сравнили $A[i]$ и $A[index_1]$.
Это означает, что второе максимальное число отличается от первого по значению, а не по индексу элемента, который мы выбрали для решения задачи.
Так, наше решение не работает при любом тесте, в котором второе самое большое число равно первому.
Теперь изменим условие: вместо
A[i] != A[index_1]
мы используем
i != index_1
Проведя стресс-тестирование еще раз, мы видим на экране шквал сообщений OK
. Ждём минуту, пока нам не надоест, и заключаем, что MaxPairwiseProductFast
наконец-то работает правильно!
Однако не стоит останавливаться на этом, так как вы сгенерировали только очень маленькие тесты с $N=5$ и $M=10$.
Теперь нужно проверить, работает ли наша программа при большем $n$ и бо́льших элементах массива.
Таким образом, мы меняем $N$ на $1,000$ (при большем $N$ примитивное решение будет довольно медленным из-за квадратичного времени выполнения).
Мы также меняем $M$ на $200,000$ и запускаем программу.
Ещё раз наблюдаем, как экран заполняется сообщениями OK
. Затем ждём минуту, а потом решаем, что MaxPairwiseProductFast
действительно работает верно.
После этого мы сдаём получившееся решение системе оценки и успешно проходим тест!
Как вы можете заметить, даже при решении такой простой задачи как Максимальное попарное произведение
сложно избежать
труднораспознаваемых ошибок на этапе проектирования и реализации алгоритма. Приведённый ниже псевдокод — это пример более надежного
способа реализации алгоритма.
MaxPairwiseProductFast(A[1..n]):
index = 1
for i from 2 to n
if A[i] > A[index]:
index = i
swap(A[index], A[n]) // поставим наибольшее значение в конец массива
index = 1:
for i from 2 to n - 1
if A[i] > A[index]:
index = i
swap(A[index], A[n - 1]) // поставим второй по величине элемент предпоследним
return A[n - 1] * A[n]
В этой книге вы узнаете, как проектировать и реализовывать алгоритмы так, чтобы минимизировать вероятность ошибок. А заодно научитесь тестировать вашу реализацию.
Ещё более быстрый алгоритм
Алгоритм MaxPairwiseProductFast
находит два самых больших числа примерно за $2n$ сравнений.
💡 Остановитесь и подумайте:
Найдите два самых больших элемента в массиве за $1.5n$ сравнений.
Когда вы решите эту задачу, вас ждет ещё более сложное упражнение. Попробуйте с ним справиться!
💡 Остановитесь и подумайте:
Найдите два самых наибольших элемента в массиве за $n + lceil log_2 n rceil – 2$ сравнений.
Если это упражнение показалось вам слишком простым, посмотрите задачи ниже. Они вполне могут оказаться на следующем собеседовании!
✒️ Упражнение:
Докажите, что не существует алгоритма, которому потребуется менее $n + lceil log_2 n rceil – 2$ сравнений, чтобы найти два самых больших элемента массива.
✒️ Упражнение:
Какой алгоритм найдёт три самых больших элемента быстрее всего?
Более компактный алгоритм
Задачу Максимальное попарное произведение
можно решить с помощью следующего компактного алгоритма, который использует сортировку (в неубывающем порядке).
MaxPairwiseProductBySorting(A[1..n]):
Sort(A)
return A[n-1]*A[n]
Этот алгоритм делает даже больше, чем нам нужно: вместо того, чтобы найти два самых больших элемента, он сортирует весь массив.
Поэтому его время выполнения $O(nlog n)$, а не $O(n)$. Однако для таких ограничений ($2 le n le 2 cdot 10^5$) он достаточно быстрый, чтобы выполнить задачу за секунду и успешно пройти тесты в нашей систему оценки.
Дан массив целых чисел, найти в нем максимальное произведение двух целых чисел.
Например, рассмотрим массив {-10, -3, 5, 6, -2}
. Максимальный продукт – это (-10, -3)
или же (5, 6)
пара.
Потренируйтесь в этой проблеме
Наивное решение состоит в том, чтобы рассматривать каждую пару элементов и вычислять их произведение. Обновите максимальное произведение, найденное на данный момент, если произведение текущей пары больше. Наконец, выведите элементы, участвующие в максимальном произведении. Это показано ниже на C, Java и Python:
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include <stdio.h> #include <limits.h> // Наивное решение для нахождения максимального произведения двух целых чисел // в массиве void findMaximumProduct(int arr[], int n) { // базовый вариант if (n < 2) { return; } int max_product = INT_MIN; int max_i, max_j; // рассматриваем каждую пару элементов for (int i = 0; i < n – 1; i++) { for (int j = i + 1; j < n; j++) { // обновить максимальный продукт, если требуется if (max_product < arr[i] * arr[j]) { max_product = arr[i] * arr[j]; max_i = i, max_j = j; } } } printf(“Pair is (%d, %d)”, arr[max_i], arr[max_j]); } int main() { int arr[] = { –10, –3, 5, 6, –2 }; int n = sizeof(arr) / sizeof(arr[0]); findMaximumProduct(arr, n); return 0; } |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
class Main { // Наивное решение для нахождения максимального произведения двух целых чисел // в массиве public static void findMaximumProduct(int[] A) { // базовый вариант if (A.length < 2) { return; } int max_product = Integer.MIN_VALUE; int max_i = –1, max_j = –1; // рассматриваем каждую пару элементов for (int i = 0; i < A.length – 1; i++) { for (int j = i + 1; j < A.length; j++) { // обновить максимальный продукт, если требуется if (max_product < A[i] * A[j]) { max_product = A[i] * A[j]; max_i = i; max_j = j; } } } System.out.print(“Pair is (“ + A[max_i] + “, “ + A[max_j] + “)”); } public static void main (String[] args) { int[] A = { –10, –3, 5, 6, –2 }; findMaximumProduct(A); } } |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import sys # Наивное решение для нахождения максимального произведения двух целых чисел в списке def findMaximumProduct(A): # Базовый вариант if len(A) < 2: return max_product = –sys.maxsize max_i = max_j = –1 # рассматривает каждую пару элементов for i in range(len(A) – 1): for j in range(i + 1, len(A)): # обновить максимальный продукт, если требуется if max_product < A[i] * A[j]: max_product = A[i] * A[j] (max_i, max_j) = (i, j) print(“Pair is”, (A[max_i], A[max_j])) if __name__ == ‘__main__’: A = [–10, –3, 5, 6, –2] findMaximumProduct(A) |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Временная сложность приведенного выше решения равна O(n2) и не требует дополнительного места, где n
это размер ввода.
Временная сложность может быть улучшена путем сортировки массива. Тогда результат будет максимальным из следующего:
- Произведение максимального и второго максимального целого числа в массиве (т. е. двух последних элементов в отсортированном массиве).
- Произведение минимального и второго минимального целых чисел в массиве (т. е. первых двух элементов в отсортированном массиве).
Ниже приведена реализация вышеуказанного алгоритма на C, Java и Python:
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <stdio.h> #include <stdlib.h> int compare(const void *a, const void *b) { return *(int*)a – *(int*)b; } // Функция для нахождения максимального произведения двух целых чисел в массиве void findMaximumProduct(int arr[], int n) { // базовый вариант if (n < 2) { return; } // сортируем массив по возрастанию qsort(arr, n, sizeof(int), compare); // выбираем максимум из следующего: // 1. Произведение первых двух элементов или // 2. Произведение двух последних элементов. if ((arr[0] * arr[1]) > (arr[n – 1] * arr[n – 2])) { printf(“Pair is (%d, %d)”, arr[0], arr[1]); } else { printf(“Pair is (%d, %d)”, arr[n – 1], arr[n – 2]); } } int main() { int arr[] = { –10, –3, 5, 6, –20 }; int n = sizeof(arr) / sizeof(arr[0]); findMaximumProduct(arr, n); return 0; } |
Скачать Выполнить код
результат:
Pair is (-20, -10)
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import java.util.Arrays; class Main { // Наивное решение для нахождения максимального произведения двух целых чисел // в массиве public static void findMaximumProduct(int[] A) { // `n` – длина массива int n = A.length; // базовый вариант if (n < 2) { return; } // сортируем массив по возрастанию Arrays.sort(A); // выбираем максимум из следующего: // 1. Произведение первых двух элементов или // 2. Произведение двух последних элементов. if ((A[0] * A[1]) > (A[n – 1] * A[n – 2])) { System.out.print(“Pair is (“ + A[0] + ‘,’ + A[1] + ‘)’); } else { System.out.print(“Pair is (“ + A[n – 1] + ‘,’ + A[n – 2] + ‘)’); } } public static void main (String[] args) { int[] A = { –10, –3, 5, 6, –20 }; findMaximumProduct(A); } } |
Скачать Выполнить код
результат:
Pair is (-20, -10)
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# Наивное решение для нахождения максимального произведения двух целых чисел в списке def findMaximumProduct(A): # `n` – длина списка n = len(A) # Базовый вариант if n < 2: return # Список сортировки # в порядке возрастания A.sort() # выберите максимум из следующего: # 1. Произведение первых двух элементов или # 2. Произведение двух последних элементов. if (A[0] * A[1]) > (A[n – 1] * A[n – 2]): print(“Pair is”, (A[0], A[1])) else: print(“Pair is”, (A[n – 1], A[n – 2])) if __name__ == ‘__main__’: A = [–10, –3, 5, 6, –20] findMaximumProduct(A) |
Скачать Выполнить код
результат:
Pair is (-20, -10)
Временная сложность приведенного выше решения равна O(n.log(n)) и не требует дополнительного места.
Мы можем решить эту задачу за линейное время, поскольку для решения этой задачи нам нужны только элементы максимума, второго максимума, минимума и второго минимума. Мы можем вычислить все это только за один обход массива, который учитывает O(n) временная сложность. Этот подход демонстрируется ниже на C, Java и Python:
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
#include <stdio.h> #include <limits.h> // Функция для нахождения максимального произведения двух целых чисел в массиве void findMaximumProduct(int arr[], int n) { // для хранения максимального и второго максимального элемента в массиве int max1 = arr[0], max2 = INT_MIN; // для хранения минимального и второго минимального элемента в массиве int min1 = arr[0], min2 = INT_MAX; for (int i = 1; i < n; i++) { // если текущий элемент больше максимального элемента, // обновить максимальный и второй максимальный элемент if (arr[i] > max1) { max2 = max1; max1 = arr[i]; } // если текущий элемент меньше максимального, но больше заданного // второй максимальный элемент, обновить второй максимальный элемент else if (arr[i] > max2) { max2 = arr[i]; } // если текущий элемент меньше минимального элемента, // обновляем минимум и второй минимум if (arr[i] < min1) { min2 = min1; min1 = arr[i]; } // если текущий элемент больше минимального, но меньше заданного // второй минимальный элемент, обновить второй минимальный элемент else if (arr[i] < min2) { min2 = arr[i]; } // иначе игнорируем элемент } // выбираем максимум из следующего: // 1. Произведение максимального элемента на второй максимальный или // 2. Произведение минимального и второго минимального элемента if (max1 * max2 > min1 * min2) { printf(“Pair is (%d, %d)”, max1, max2); } else { printf(“Pair is (%d, %d)”, min1, min2); } } int main() { int arr[] = { –10, –3, 5, 6, –2 }; int n = sizeof(arr) / sizeof(arr[0]); findMaximumProduct(arr, n); return 0; } |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
class Main { // Функция для нахождения максимального произведения двух целых чисел в массиве public static void findMaximumProduct(int[] A) { // для хранения максимального и второго максимального элемента в массиве int max1 = A[0], max2 = Integer.MIN_VALUE; // для хранения минимального и второго минимального элемента в массиве int min1 = A[0], min2 = Integer.MAX_VALUE; for (int i = 1; i < A.length; i++) { // если текущий элемент больше максимального элемента, // обновить максимальный и второй максимальный элемент if (A[i] > max1) { max2 = max1; max1 = A[i]; } // если текущий элемент меньше максимального, но больше заданного // второй максимальный элемент, обновить второй максимальный элемент else if (A[i] > max2) { max2 = A[i]; } // если текущий элемент меньше минимального элемента, // обновляем минимум и второй минимум if (A[i] < min1) { min2 = min1; min1 = A[i]; } // если текущий элемент больше минимального, но меньше заданного // второй минимальный элемент, обновить второй минимальный элемент else if (A[i] < min2) { min2 = A[i]; } // иначе игнорируем элемент } // выбираем максимум из следующего: // 1. Произведение максимального элемента на второй максимальный или // 2. Произведение минимального и второго минимального элемента if (max1 * max2 > min1 * min2) { System.out.print(“Pair is (“ + max1 + “, “ + max2 + “)”); } else { System.out.print(“Pair is (“ + min1 + “, “ + min2 + “)”); } } public static void main (String[] args) { int[] arr = { –10, –3, 5, 6, –2 }; findMaximumProduct(arr); } } |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import sys # Функция для нахождения максимального произведения двух целых чисел в списке def findMaximumProduct(A): # для хранения максимального и второго максимального элемента в списке max1 = A[0] max2 = –sys.maxsize # для хранения минимального и второго минимального элемента в списке min1 = A[0] min2 = sys.maxsize for i in range(1, len(A)): #, если текущий элемент больше максимального элемента, # обновляет максимальный и второй максимальный элемент if A[i] > max1: max2 = max1 max1 = A[i] #, если текущий элемент меньше максимального, но больше # второй максимальный элемент, обновить второй максимальный элемент elif A[i] > max2: max2 = A[i] #, если текущий элемент меньше минимального элемента, # обновить минимум и второй минимум if A[i] < min1: min2 = min1 min1 = A[i] #, если текущий элемент больше минимального, но меньше # второй минимальный элемент, обновить второй минимальный элемент elif A[i] < min2: min2 = A[i] # в противном случае игнорировать элемент # выберите максимум из следующего: # 1. Произведение максимального и второго максимального элемента или # 2. Произведение минимального и второго минимального элемента if max1 * max2 > min1 * min2: print(“Pair is”, (max1, max2)) else: print(“Pair is”, (min1, min2)) if __name__ == ‘__main__’: A = [–10, –3, 5, 6, –2] findMaximumProduct(A) |
Скачать Выполнить код
результат:
Pair is (-10, -3)
Упражнение: Найдите максимальное произведение трех целых чисел в массиве
Спасибо за чтение.
Пожалуйста, используйте наш онлайн-компилятор размещать код в комментариях, используя C, C++, Java, Python, JavaScript, C#, PHP и многие другие популярные языки программирования.
Как мы? Порекомендуйте нас своим друзьям и помогите нам расти. Удачного кодирования 🙂
I was asked this algorithm question during my onsite interview. Since I was not asked to sign NDA, I post it here for an answer.
Given an array of REAL numbers that does not contain 0, find the consecutive elements that yield max product. The algorithm should run in linear time
I have considered the following approach:
Use two arrays. First one is to use DP idea to record the current max absolute value product, the second array to record the number of negative elements met so far. The final result should be the largest max absolute value and the number of negative numbers be even.
I thought my method will work, but was interrupted during coding saying it will not work.
Please let me know what is missing in the above approach.
pal4life
3,1504 gold badges35 silver badges57 bronze badges
asked Sep 17, 2013 at 1:10
12
The algorithm is indeed O(n). When iterating the array, use a variable to store the max value found so far, a variable to store the max value of subarray that ends at a[i], and another variable to store minimum value that ends at a[i] to treat negative values.
float find_maximum(float arr[], int n) {
if (n <= 0) return NAN;
float max_at = arr[0]; // Maximum value that ends at arr[i]
float min_at = arr[0]; // Minimum value that ends at arr[i]
float max_value = max_at;
for (int i = 1; i < n; i++) {
float prev_max_at = max_at, prev_min_at = min_at;
max_at = max(arr[i], arr[i] * prev_min_at, arr[i] * prev_max_at);
min_at = min(arr[i], arr[i] * prev_min_at, arr[i] * prev_max_at);
max_value = max(max_value, max_at);
}
return max_value;
}
answered Sep 17, 2013 at 2:13
Chen PangChen Pang
1,37010 silver badges11 bronze badges
9
You can implement a variant of the Kadane algorithm (http://en.wikipedia.org/wiki/Maximum_subarray_problem) who runs with constant extra memory and linear in the size of the problem (no extra array,…)
If only strict positive numbers are given:
def max_subarray_mul(A):
max_ending_here = max_so_far = 1
for x in A:
if x > 0
max_ending_here = max(1,max_ending_here*x)
max_so_far = max(max_so_far, max_ending_here)
return max_so_far
I’m still working on the part with negative numbers
Or a more expensive (in time) method is the following, but this will work with negative numbers:
def max_subarray_mul(A):
max_so_far = 1
n = length(A)
for i in 1...n:
x = A[i]
tmp = x
max_so_far = max(max_so_far,tmp)
for j in i+1...n:
tmp = tmp*A[j]
max_so_far = max(max_so_far,tmp)
return max_so_far
Which runs in constant memory and O(n²)
time
answered Sep 17, 2013 at 1:28
Willem Van OnsemWillem Van Onsem
434k29 gold badges419 silver badges540 bronze badges
1
Using python notations:
- compute
min( prod( v[ 0: ] ), prod( v[ 1: ] ), ..., prod( v[ -1 ] ) )
andmax( prod( v[ 0: ] ), prod( v[ 1: ] ), ..., prod( v[ -1 ] ) )
in O(n) - compute recursively the max product based on the fact that
maxpro(v) = max( maxpro(v[:-1]) * max( prod( v[ 0: ] ), prod( v[ 1: ] ), ..., prod( v[ -1 ] ) )
. This is O(n) too
Here is the code:
#
n = 5
vmax = 10
#
v = nr.randint( 1, vmax, n )
v *= nr.randint( 0, 2, n ) * 2 - 1
#
print v
#
prod_res = np.zeros( ( 2, n ), int )
prod_res[ 0, 0 ] = prod_res[ 1, 0 ] = v[ 0 ]
for i in xrange( 1, n ) :
prod_res[ 0, i ] = min( v[ i ], prod_res[ 1, i-1 ] * v[ i ], prod_res[ 0, i-1 ] * v[ i ] )
prod_res[ 1, i ] = max( v[ i ], prod_res[ 1, i-1 ] * v[ i ], prod_res[ 0, i-1 ] * v[ i ] )
#
print prod_res
#
def maxpro_naive( v ) :
return v[ 0 ] if ( len( v ) == 1 ) else max( maxpro_naive( v[ :-1 ] ), prod_res[ 1, len(v) -1 ] )
#
print maxpro_naive( v )
answered Sep 17, 2013 at 3:36
usual meusual me
8,2289 gold badges51 silver badges93 bronze badges
Ignoring negative numbers for the moment…
Let A[i..j]
mean A[i]*A[i+1]*...*A[j]
The problem is to find max(A[i..j])
Notice that A[i..j] = A[0..j] / A[0..i-1]
So if we calculate A[0..x]
for all x.
We can then determine max(A[i..j]) = max(A[0..x]) / min(A[0..y])
answered Sep 17, 2013 at 5:54
Andrew TomazosAndrew Tomazos
65.4k39 gold badges181 silver badges310 bronze badges
1
Taking care of the thing if there are no 1’s in the array and the product coming should not be 1 in that case.
Here is my code:
#include<bits/stdc++.h>
using namespace std;
int max(int x, int y)
{ return (y > x)? y : x; }
int min(int x, int y)
{ return (y < x)? y : x; }
bool search(int a[],int k,int n)
{
for(int i=0;i<n;i++)
{
if(a[i]==k)
return true;
}
return false;
}
int maxSubArrayProduct(int a[], int size)
{
int maxpos = 1, minneg=1, i;
int pro_max = 1;
for (i = 0; i < size; i++)
{
if(a[i]<0)
{
int temp=maxpos;
maxpos=max(maxpos,minneg*a[i]);
minneg=min(minneg,temp*a[i]);
}
if(a[i]==0)
{maxpos=1;minneg=1;}
if(a[i]>0)
{
maxpos=maxpos*a[i];
minneg=min(minneg,minneg*a[i]);
}
if(pro_max<maxpos)
pro_max=maxpos;
}
return pro_max;
}
/* Driver program to test maxSubArrayProduct */
int main()
{
int a[] = {-1,0,1};
int n = sizeof(a)/sizeof(a[0]);
int start=0,end=0;
int max_pro = maxSubArrayProduct(a, n);
if(max_pro==1)
if(search(a,1,n))max_pro=1;
else max_pro=0;
printf("Maximum contiguous product is %dn", max_pro);
return 0;
}
answered Jul 29, 2015 at 6:13
In O(n) result. Find the consecutive elements that yield max product, by multiplying each element from left to right and saving them in a list. If the new product is bigger than the last multiply the next element and update the list. If not start a new list and repeat. Algorithm in Python 3.3 :
import numpy as np
x = [-500,-400,200,0.1,-100,20,-10,2]
prod_seq_lists = [[x[0], x[1]]] # Start assuming the first 2 elements have max product and save them in a list
product_result = [] # Contains the product of each list
for e in x[2:]: # Start for loop from 3rd element
if x[0] == 0 or x[1] == 0 or e == 0: # Raise error if there's a 0
raise IndexError('Found 0')
temp_b = np.prod(prod_seq_lists[-1]) # Calculate the product of the last list in max_prod_seq
temp_a = temp_b * e # Multiply the new_element
if temp_a >= temp_b: # If last_list*new_element >= last_list
prod_seq_lists[-1].append(e) # Append the new_element in your last_list
if e == x[-1]:
product_result.append(temp_a) # Save the product of the last list
else:
product_result.append(temp_b) # Save the product of each list
prod_seq_lists.append([e]) # Else, append append the new element in a new_list
print("Your array: ", prod_seq_lists)
print("The list with max product of consecutive elements: ", prod_seq_lists[np.argmax(product_result)]) # Get index of the maximum product and print that list
print("The max product of consecutive elements: ", max(product_result))
Returns :
Your array: [[-50, -40, 20], [0.1], [-100], [20], [-10], [90, 1000]]
The list with max product of consecutive elements: [90, 1000]
The max product of consecutive elements: 90000
answered Sep 18, 2015 at 20:10
TseriTseri
312 bronze badges
I wrote below code, for finding maximum product of adjacent integer values in input array, assuming the product would also be in the int range
it would iterate the loop n/2 times only
int adjacentElementsProduct(int[] inputArray) {
int maxProdct=inputArray[0]*inputArray[1];
//as we have already taken product of first two , start from 3rd and iterate till second last because we are checking the product of i+1 for every i
for (int i=2; i<inputArray.length-1; i=i+2){
if(inputArray[i-1]*inputArray[i] >inputArray[i]*inputArray[i+1]){
if(inputArray[i-1]*inputArray[i]>maxProdct)
maxProdct =inputArray[i-1]*inputArray[i];
}
else if(inputArray[i+1]*inputArray[i] > maxProdct)
maxProdct=inputArray[i+1]*inputArray[i];
}
//if its an even array the last element would have been covered while calculating product with second last, otherwise we would check the product for last and second last element and compare with maxProduct
if(inputArray.length%2 !=0){
if(maxProdct<inputArray[inputArray.length-1]*inputArray[inputArray.length-2]){
maxProdct=inputArray[inputArray.length-1]*inputArray[inputArray.length-2];
}
}
return maxProdct;
}
answered Mar 28, 2017 at 2:26
LearnerLearner
1,5448 gold badges28 silver badges55 bronze badges
If we want to solve in O(n) and allowed to take two traversals of array and o(n) extra space, my below code would work for all +ve and -ve values in Java.
import java.util.ArrayList;
import java.util.List;
public class largestProductOfTwoNumbers {
public static void main(String[] args) {
int result = 0;
int a[] = { -22, -5, 12, 6, 3, 4, 9, -11, 4, 5, 6, 8, 7, 7 };
int max = 0;
int curr = 0;
List<Integer> list = new ArrayList();
for (int i = 0; i < a.length - 1; i++) {
curr = a[i] * a[i + 1];
list.add(curr);
}
for (int i = 0; i < list.size(); i++) {
if (list.get(i) > max) {
max = list.get(i);
}
}
System.out.println(max);
}
}
Stphane
3,3685 gold badges31 silver badges47 bronze badges
answered Oct 26, 2019 at 16:45
This answer assumes all numbers are positive.
A one-liner solution in python:
from itertools import accumulate
def max_prod(a):
return max(accumulate(a, lambda x,y: max(x*y, 1), initial=1))
This is O(n)-time and O(1)-space.
This algorithm uses itertools.accumulate
to compute accumulated products from the beginning of the array, dropping back to the empty product 1 if a result is ever lower than 1. Then, we return the maximum product found.
We can test the correctness of the algorithm by generating arrays of random numbers and comparing with a bruteforce solution:
from math import prod
from numpy import random
def brute_force(a):
return max(prod(a[i:j]) for i in range(len(a)) for j in range(i,len(a)+1))
for _ in range(1000):
a = random.random(10) * 3 # array of 10 floats in [0.0..3.0]
p1 = max_prod(a)
p2 = brute_force(a)
if p1 != p2:
print(a)
print(list(accumulate(a, lambda x,y: max(x*y, 1), initial=1)))
print(p1, p2)
print()
answered Nov 25, 2021 at 15:29
StefStef
12.7k2 gold badges17 silver badges28 bronze badges
Задача, которую предлагали на собеседованиях в Apple: у вас есть массив с целыми числами, в том числе и отрицательными, вам нужно найти самое большое произведение 3 чисел из этого массива.
Например: у вас есть массив list_of_ints, содержащий числа -10, -10, 1, 3, 2. Функция, которая обрабатывает этот массив, должна вернуть 300, так как -10 * -10 * 3 = 300. Задание нужно выполнить максимально эффективно, не забывая про отрицательные числа.
Решение
Методов решения много, но не так просто добиться O(n) времени выполнения и O(1) затрат памяти. Для эффективного решения задачи мы создадим и будем наблюдать за состоянием следующих переменных:
- highest_product_of_three
- highest_product_of_2
- highest
- lowest_product_of_2
- lowest
Когда мы пройдемся по массиву до конца, в highest_product_of_three будет содержаться наш ответ, а остальные переменные мы используем как временный буфер. highest_product_of_2 и lowest_product_of_2 будут содержать наибольшее произведение из двух и наименьшее произведение из двух соответственно, а проходя по массиву, мы будем проверять произведение текущего числа current с этими переменными (отрицательный current с lowest_product_of_2 и положительный с highest_product_of_2). highest и lowest нам нужны для запоминания минимального и максимального чисел в массиве.
Код решения на Python:
def highest_product_of_3(list_of_ints):
# Проверим, чтобы в массиве было 3 и больше чисел.
if len(list_of_ints) < 3:
raise Exception('Less than 3 items!')
# Мы начнем с 3-его элемента массива (с индекса 2),
# так как первые 2 элемента уже сразу пойдут
# в highest_product_of_2 и lowest_product_of_2.
highest = max(list_of_ints[0], list_of_ints[1])
lowest = min(list_of_ints[0], list_of_ints[1])
highest_product_of_2 = list_of_ints[0] * list_of_ints[1]
lowest_product_of_2 = list_of_ints[0] * list_of_ints[1]
# Также вычислим highest_product_of_three из первых 3-х элементов.
highest_product_of_three = list_of_ints[0] * list_of_ints[1] * list_of_ints[2]
# Начинаем проход по массиву с индекса 2.
for current in list_of_ints[2:]:
# проверяем возможность увеличить highest_product_of_three
# или оставляем его как есть.
highest_product_of_three = max(
highest_product_of_three,
current * highest_product_of_2,
current * lowest_product_of_2)
# То же самое проверим и на максимальном произведении из двух.
highest_product_of_2 = max(
highest_product_of_2,
current * highest,
current * lowest)
# И на минимальном произведении из двух.
lowest_product_of_2 = min(
lowest_product_of_2,
current * highest,
current * lowest)
# Появилось ли новое максимальное число?
highest = max(highest, current)
# Появилось ли новое минимальное число?
lowest = min(lowest, current)
return highest_product_of_three
Сложность алгоритма — O(n) по времени выполнения и O(1) по памяти.
Источник: Interview Cake
Имеется число, которое мы раскладываем на простые множители. Нужно найти такое произведение его элементов, чтобы оно было максимально возможным для заданного промежутка, если для заданного промежутка невозможно найти такое произведение, то следует вывести -1.
using System.Collections.Generic;
namespace probka
{
class Program
{
static List<int> Delitely(int k)//Нахождение всех делителей
{
var Delitely = new List<int>();
var Del = 2;
while (k % Del == 0)
{
Delitely.Add(Del);
k /= Del;
}
Del = 3;
while (Math.Pow(Del, 2) <= k)
{
if (k % Del == 0)
{
Delitely.Add(Del);
k /= Del;
}
else
{
Del += 2;
}
}
if (k > 1)
{
Delitely.Add(k);
}
return Delitely;
}
static void Main(string[] args)
{
int a = int.Parse(Console.ReadLine());
int[] mass = Delitely(a).ToArray();//массив простых множителей числа а
int max = a;
Console.WriteLine("Все делители: " + string.Join(" ", mass));
int start = 1000;
int end = 10000;
int i = 0;
if ((max >= start) && (max <= end))//если число уже входит в нужный промежуток
max = max;
else if (max < start)//если число меньше минимальной границы
max = -1;
else
while (max > end)
{
max /= mass[i];
i++;
if (i>=mass.Length)
{
max = -1;
break;
}
}
Console.WriteLine(max);
}
}
}
Если просто находить всевозможные произведения чисел, то программа получается неэффективная(при больших числах количество множителей много), поэтому я шел от самого числа и делил его на множители начиная сначала, но это идея провалилась, так как при введение числа а=1000000 программа выводит 3125, в то время , как 5×5×5×5×5×2=6250.