I am working on a code challenge problem -- \"find lucky triples\". \"Lucky triple\" is defined as \"In a list lst
, for any combination of triple like (ls
A precomputation
step to the problem can help reduce time complexity.
Precomputation Step:
For every element(i), iterate the array to find which are the elements(j) such that lst[j]%lst[i]==0
for(i=0;i<n;i++)
{
for(j=i+1;j<n;j++)
{
if(a[j]%a[i] == 0)
// mark those j's. You decide how to store this data
}
}
This Precomputation Step will take O(n^2)
time.
In the Ultimate Step, use the details of the Precomputation Step, to help find the triplets..
Read up on the Sieve of Eratosthenes, a common technique for finding prime numbers, which could be adapted to find your 'lucky triples'. Essentially, you would need to iterate your list in increasing value order, and for each value, multiply it by an increasing factor until it is larger than the largest list element, and each time one of these multiples equals another value in the list, the multiple is divisible by the base number. If the list is sorted when given to you, then the i < j < k requirement would also be satisfied.
e.g. Given the list [3, 4, 8, 15, 16, 20, 40]
:
Start at 3
, which has multiples [6, 9, 12, 15, 18 ... 39]
within the range of the list. Of those multiples, only 15
is contained in the list, so record under 15
that it has a factor 3
.
Proceed to 4
, which has multiples [8, 12, 16, 20, 24, 28, 32, 36, 40]
. Mark those as having a factor 4.
Continue through the list. When you reach an element that has an existing known factor, then if you find any multiples of that number in the list, then you have a triple. In this case, for 16
, this has a multiple 32
which is in the list. So now you know that 32
is divisible by 16
, which is divisible by 4
. Whereas for 15
, that has no multiples in the list, so there is no value that can form a triplet with 3
and 15
.
A simple dynamic programming-like algorithm will do this in quadratic time and linear space. You just have to maintain a counter c[i]
for each item in the list, that represents the number of previous integers that divides L[i]
.
Then, as you go through the list and test each integer L[k]
with all previous item L[j]
, if L[j]
divides L[k]
, you just add c[j]
(which could be 0) to your global counter of triples, because that also implies that there exist exactly c[j]
items L[i]
such that L[i]
divides L[j]
and i < j
.
int c[] = {0}
int nbTriples = 0
for k=0 to n-1
for j=0 to k-1
if (L[k] % L[j] == 0)
c[k]++
nbTriples += c[j]
return nbTriples
There may be some better algorithm that uses fancy discrete maths to do it faster, but if O(n^2) is ok, this will do just fine.
In regard to your comment:
Why DP? We have something that can clearly be modeled as having a left to right order (DP orange flag), and it feels like reusing previously computed values could be interesting, because the brute force algorithm does the exact same computations a lot of times.
How to get from that to a solution? Run a simple example (hint: it should better be by treating input from left to right). At step i
, compute what you can compute from this particular point (ignoring everything on the right of i), and try to pinpoint what you compute over and over again for different i's
: this is what you want to cache. Here, when you see a potential triple at step k
(L[k] % L[j] == 0
), you have to consider what happens on L[j]
: "does it have some divisors on its left too? Each of these would give us a new triple. Let's see... But wait! We already computed that on step j
! Let's cache this value!" And this is when you jump on your seat.
Full working solution in python:
c = [0] * len(l)
print c
count = 0
for i in range(0,len(l)):
j=0
for j in range(0, i):
if l[i] % l[j] == 0:
c[i] = c[i] + 1
count = count + c[j]
print j
print c
print count