1

For a simple algorithmic challenge, I have to make an algorithm to solve this:

You are given an array of n integers, a0, a1,...,an-1, and a positive integer, k. Find and print the number of (i, j) pairs where i < j and ai + aj is evenly divisible by k.

The obvious brute force algorithm is O(n2) time and O(1) space (I think). Pseudo-code:

int count;
for each (i in array) {
    for each (j in array) {
        if (i >= j)
            continue;

        if ((i + j) % k == 0)
            count = count + 1;
    }
}

I don't think there is a way - there probably is :) - without iterating 2 times through the array to count every pair, because every pair (except those where i >= j of course) has a possible chance of being evenly divisible by k, so I have to iterate through all of them.

So, is there any way to optimize my algorithm?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • yep, you can, check it http://stackoverflow.com/questions/16605991/number-of-subarrays-divisible-by-k – levi Aug 16 '16 at 03:09
  • @levi I think its a bit of different problem statement than I have. – Rakete1111 Aug 16 '16 at 03:12
  • 2
    here is another answer http://stackoverflow.com/questions/12545544/optimal-algorithm-needed-for-finding-pairs-divisible-by-a-given-integer-k – levi Aug 16 '16 at 03:14
  • @levi Nice find, I came up with the exact same algorithm, as the answerer there, so I better upvote his answer lol. – Paul Aug 16 '16 at 03:35

1 Answers1

3

You can walk through the array once and keep track of the number of elements that have each remainder up to k-1, when they are divided by k. That takes O(k) space, but allows you to solve the problem in O(n + k) steps, which is much better if k is small.

Pseudocode (AKA JavaScript):

function countDivisiblePairs( arr, k ) {
    const remainders = Array( k ).fill( 0 );
    for ( let i = 0; i < arr.length; i++ ) {
        remainders[ arr[ i ] % k ]++; 
    }

    // Count the pairs of elements that were individually 
    // divisible by `k` (had a remainder of `0`), so 
    // added together they are still divisible by k
    //
    // `remainders[ 0 ]*remainders[ 0 ]` of them if we wanted all, but 
    // since we only want to count when i < j, so we subtract 
    // `remainders[0]` to get rid of the cases where `i = j` and then 
    // we divide by 2 to remove the cases where `i > j`.

    let numPairs = (remainders[ 0 ]*remainders[ 0 ] - remainders[ 0 ])/2;

    // Count the cases where the remainder wasn't 0,
    // if the remainders sum to `k`, then the sum is divisible by `k`.
    for ( let i = 1; i <= k/2; i++ ) {

        // Note that i + (k - i) = k, so each elements with a 
        // remainder of `i` can be paired with each element with
        // a remainder of `k`, but, if `k` was even there will be a 
        // final iteration where `i = (k - i)`, so we need to add a 
        // special case to only count when `i < j`, we do it the same 
        // way as we did for the case when the remainder was 0. with 
        // the if statement `(2*i === k)` is another way of writing
        // the expression `i === (k - i)`.

        if ( 2*i === k ) 
            numPairs += (remainders[ i ]*remainders[ i ] - remainders[ i ])/2;
        else
            numPairs += remainders[ i ]*remainders[ k - i ];
    }
    return numPairs;
}

The code above assumes that there are no duplicates in the input array. In that case you need special care EG, [2,2,4,4,4], 2 should ouput 6 but instead outputs 10 (the correct output for [2,4,6,8,10], 2) but it should give you the jist of the algorithm. Output:

countDivisiblePairs( [1,2,3,4,5,6,7,8], 2 ); // 12
countDivisiblePairs( [1,2,3,4,5,6,7,8], 3 ); // 10
countDivisiblePairs( [1,2,3,4,5,6,7,8], 4 ); // 6
countDivisiblePairs( [1,2,3,4,5,6,7,8], 5 ); // 6

Note that the special case in the loop only happens on the last iteration and only when k is even, EG. if k is 4, then when i = 2, i === (k - i), so if we didn't have the special handling we would count extra elements. EG for the example output where I used a k of 4, there are two elements that have a remainder of 2: [2,6]. If we didn't have the extra handling it would say there are four ways to pair them, amounting to (2,2),(2,6),(6,2),(6,6), but the extra logic subtracts the cases where they're paired with themselves, so we have (2,6),(6,2) remaining and then the division by 2 subtracts the case where i > j, so you're left with only one pair being counted: (2,6).

Paul
  • 139,544
  • 27
  • 275
  • 264