Fastest way to find minimal product of 2 array elements containing 200000+ elements
Assuming there is at least one pair of elements satisfying the conditions and no multiplication of two elements in it overflows, this can be done in Theta(n-k)
time and Theta(1)
space worst- and best-case, with something like this:
auto back_max = a[0];
auto back_min = a[0];
auto best = a[0]*a[k+1];
for(std::size_t i=1; i<n-(k+1); ++i) {
back_max = std::max(back_max, a[i]);
back_min = std::min(back_min, a[i]);
best = std::min(best, std::min(a[i+k+1]*back_max, a[i+k+1]*back_min));
}
return best;
This is optimal in terms of asymptotic worst-case complexity for both time and space because the optimal product may be a[0]
with any of the n-(k+1)
elements in distance at least k+1
, so at least n-(k+1)
integers need to be read by any algorithm solving the problem.
The idea behind the algorithm is as follows:
The optimal product uses two elements of a
, assume these are a[r]
and a[s]
. Without loss of generality we can assume that s > r
since the product is commutative.
Due to the restriction abs(s-r) > k
this implies that s >= k+1
. Now s
could be each of the indices satisfying this condition, so we iterate over these indices. That is the iteration over i
in the shown code, but it is shifted by k+1
for convenience (doesn't really matter). For each iteration we need to find the optimal product involving i+k+1
as largest index and compare it with the previous best guess.
The possible indices to pair i+k+1
with are all indices smaller or equal i
due to the distance requirement. We would need to iterate over all of these as well, but that is unnecessary because the minimum of a[i+k+1]*a[j]
over j
at fixed i
is equal to min(a[i+k+1]*max(a[j]), a[i+k+1]*min(a[j]))
due to monotonicity of the product (taking the minimum with respect to both the minimum and maximum over a[j]
accounts for the two possible signs of a[i+k+1]
or equivalently the two possible directions of monotonicity.)
Since the set of a[j]
values over which we optimize here is just {a[0], ..., a[i]}
, which simply growths by one element (a[i]
) in each iteration of i
, we can simply keep track of max(a[j])
and min(a[j])
with single variables by updating them if a[i]
is larger or smaller than the previous optimal values. This is done with back_max
and back_min
in the code example.
The first step of the iteration (i=0
) is skipped in the loop and instead performed as initialization of the variables.
Not sure about fastest.
For the simpler problem without i < j - k, the minimal product is among the products of pairs from the two smallest and largest elements.
So, (the following is too complicated, see walnut's answer)
( • balk if k ≤ n
• initialise minProduct to a[0]*a[k+1])
- keep two dynamic minmax data structures upToI and beyondIplusK
starting with { } and { a[j] | k ≤ j } - for each i from 0 to n - k - 1
- add a[i] to upToI
- remove a[i+k] from beyondIplusK
- check for new minimal product among
min(upToI)×min(beyondIplusK), min(upToI)×max(beyondIplusK),
max(upToI)×min(beyondIplusK) and max(upToI)×max(beyondIplusK)
For "minimum magnitude"
Find the 2 "smallest magnitude" elements, then (after you've either found two zeros or searched the whole array), multiply them.
For "lowest value" without the abs(i - j) > k
part
There are 3 possibilities:
the two highest (smallest magnitude) negative numbers
the two lowest (smallest magnitude) non-negative numbers
the lowest (largest magnitude) negative number and the highest (largest magnitude) non-negative number
You could search for all 6 values and figure out the products and which is best at end.
However; as soon as you see a zero you know you don't need to know any more about the first 2 possibilities; and as soon as you see one negative number and one non-negative number you know that you only care about the third possibility.
This leads to a finite state machine with 3 states - "care about all 3 possibilities", "answer is zero unless a negative number is seen" and "only care about the last possibility". This can be implemented as a set of 3 loops, where 2 of the loops jump into (goto
) the middle of another loop when the state (of the finite state machine) changes.
Specifically, it might looks something vaguely like (untested):
// It could be any possibility
for(ll i=0;i<n;i++) {
if(a[i] >= 0) {
if(a[i] < lowestNonNegative1) {
lowestNonNegative2 = lowestNonNegative1;
lowestNonNegative1 = a[i];
}
if(lowestNonNegative2 == 0) {
goto state2;
}
} else {
if(a[i] > highestNegative1) {
highestNegative2 = highestNegative1;
highestNegative1= a[i];
}
if(lowestNonNegative1 < LONG_MAX) {
goto state3;
}
}
}
if(lowestNonNegative2 * lowestNonNegative1 < highestNegative2 * highestNegative1) {
cout << lowestNonNegative2 * lowestNonNegative1;
} else {
cout << highestNegative2 * highestNegative1;
}
return;
// It will be zero, or a negative and a non-negative
for(ll i=0;i<n;i++) {
state2:
if(a[i] < 0) {
goto state3;
}
}
cout << "0";
return;
// It will be a negative and a non-negative
for(ll i=0;i<n;i++) {
state3:
if(a[i] < lowestNegative) {
lowestNegative = a[i];
} else if(a[i] > highestNonNegative) {
highestNonNegative = a[i];
}
}
cout << lowestNegative * highestNonNegative;
return;
For "lowest value" with the abs(i - j) > k
part
In this case you still have the 3 possibilities; and could make it work with the same "3 loops with finite state machine" approach but it gets too messy/ugly. For this case a better alternative is likely to pre-scan the array to determine if there are any zeros and if they're all negative or all positive; so that after the pre-scan you can either know the answer is zero or select a loop designed for the specific possibility alone.