13

I have an array a[n]. The number n is entered by us. I need to find the minimal product of a[i] and a[j] if:

1) abs(i - j) > k

2) a[i] * a[j] is minimised

Here is my solution (very naive):

#include <iostream>
using namespace std;
#define ll long long
int main() {
    ll n,k; cin >> n >> k;

    ll a[n]; for(ll i=0;i<n;i++) cin >> a[i];

    ll mn; bool first = true;

    for(ll i=0;i<n;i++) {
        for(ll j=0;j<n;j++) {
            if(i!=j)
            if(abs(i-j) > k) {
                if(first) {
                    mn = a[i]*a[j];
                    first = false;
                } else if(a[i]*a[j] < mn) mn = a[i]*a[j];
            }
        }
    }
    cout << mn << endl;
}

But I want to know if there is any faster way to find a minimal product with distance?

Mouvre
  • 264
  • 2
  • 9
  • 7
    [Why should I not #include ?](https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h/31816096) and C++ only provide a *Variable Length Array* by compiler extension. Why are you not using `std::vector`? @Scheff - sorting would destroy the original "distance" relationships. – David C. Rankin Jan 12 '20 at 06:59
  • 3
    At least the check `if (i!=j) if (abs(i - j) > k)` can be eliminated. Just start the inner loop at i + k + 1: `for (ll j = i + k + 1; j < n; ++j)`. The check with `first` can be eliminated as well if `mn` is initialized prior e.g. with `mn = a[0] * a[k + 1];`. (Maybe, `k` should be checked against `n` initially to make this bullet-proof.) But it's still O(N²). This must be doable faster... – Scheff's Cat Jan 12 '20 at 07:13
  • are there negative numbers in your array ? – lenik Jan 12 '20 at 07:15
  • @lenik Do you think negative numbers in the array make a difference? – Scheff's Cat Jan 12 '20 at 07:17
  • @Scheff when you multiply two very small negative numbers the result becomes a very large positive number. so the sorting stops working properly. – lenik Jan 12 '20 at 07:18
  • I guess it can be done in O(2N) (which is effectively O(N)): 1. Linear search for the minimal number in array. 2. Linear search for the second minimal number, starting at index of first + k + 1 and checking for difference to first as well. Finally, multiply both found numbers. – Scheff's Cat Jan 12 '20 at 07:23
  • @Scheff if you sort the array and multiply `a[0] * a[1]` -- this won't be the smallest product in case there are negative numbers. – lenik Jan 12 '20 at 07:23
  • @lenik Damn, I forgot that the product of two negatives is positive... – Scheff's Cat Jan 12 '20 at 07:24
  • @lenik So, in this case if the first found is negative, the search for the second must be turned into searching for maximum positive? (I'm afraid I shouldn't continue to puzzle before having had a cup of coffee...) – Scheff's Cat Jan 12 '20 at 07:27
  • @Scheff yeah... the proximity limitations might easily weed out the N smallest numbers, because they are too close to each other. So, once you sort, you still have to traverse the array, which kind of defies the purpose. – lenik Jan 12 '20 at 07:30
  • @lenik I discarded the idea with sort a while ago... ;-) – Scheff's Cat Jan 12 '20 at 07:30
  • *But I want to know if there is any faster way to find minimal product with distance?* -- There are probably hundreds, if not thousands of URL links that shows answers to this question. – PaulMcKenzie Jan 12 '20 at 07:31
  • @Scheff why is that? make a `std::pair( number, position )`, sort away and have fun! =) – lenik Jan 12 '20 at 07:32
  • Well, the @OP asked, and one answer could be "use google". @OP -- `ll a[n];` --don't do this. First, this is not valid C++ . Second, even it were valid, the stack memory will more than likely not be able to hold such an array. – PaulMcKenzie Jan 12 '20 at 07:32
  • @lenik This is something which I considered as well. But for large input data it will result in double large intermediate data... – Scheff's Cat Jan 12 '20 at 07:33
  • 2
    @PaulMcKenzie Please show *one* query with no less than two *useful* hits among the first ten for *minimal product with index distance* (or maximal). – greybeard Jan 12 '20 at 08:27
  • 1
    @PaulMcKenzie "There are probably hundreds, if not thousands of URL links that shows answers to this question." -- please share at least three of these URLs. – גלעד ברקן Jan 12 '20 at 12:42
  • 2
    Where did this question come from? It does not sound like something just made up out of thin air. I wouldn't be surprised if it is from one of those "online judge" sites. If so, on those sites are probably long drawn-out discussions on solving the problem, if not full solutions. – PaulMcKenzie Jan 12 '20 at 20:47

3 Answers3

12

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.

walnut
  • 20,566
  • 4
  • 18
  • 54
  • 3
    @greybeard I don't need to keep them around, because the only possible candidates for an optimal product with `a[i+k+1]` are the minimum and maximum. – walnut Jan 12 '20 at 07:53
  • could you explain why the algorithm works in your answer? – MinaHany Jan 12 '20 at 19:38
6

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] | kj }
  • 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)
greybeard
  • 2,015
  • 5
  • 20
  • 51
  • This should be the fastest, at least complexity-wise. It is O(n) time and storage. – smttsp Jan 12 '20 at 07:45
  • the original solution has the complexity O(N**2), how do you estimate the complexity of your solution ? – lenik Jan 12 '20 at 07:45
  • O(nlogn) time, O(n) space (for suitable minmax implementations) – greybeard Jan 12 '20 at 07:46
  • @greybeard. Why do you need n*logn time. Why not simply keeping a 4*n array that contains `minUpto`, `maxUpto`, `minBeyond`, `maxBeyond` (You can create in two iterations)? Then, in the third iteration, for each index, find the min possible multiplication. – smttsp Jan 12 '20 at 07:53
  • (@smttsp That would be an alternative step in the direction of [walnut's *solution*](https://stackoverflow.com/a/59701925).) – greybeard Jan 12 '20 at 08:06
4

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.

Brendan
  • 26,293
  • 1
  • 28
  • 50