I would like to find the highest value m = a*b that satisfies some condition C(m), where
1 <= a <= b <= 1,000,000.
In order to do
Below code will generate (and print):
[(5, 5), (4, 5), (4, 4), (3, 5), (3, 4), (2, 5), (3, 3), (2, 4), (2, 3), (1, 5), (1, 4), (2, 2), (1, 3), (1, 2), (1, 1)]
which is basically what you want, since the code can break early if your condition is satisfied. I think the whole point of this question is NOT to generate all possible combinations of (a, b)
.
The key point of the algorithm is that in each iteration, we need to consider (a - 1, b)
and (a, b - 1)
. If a == b
, however, since a <= b
, we only need to consider (a - 1, b)
. The rest is about maintaining order in the queue of tuples, Q
, based on their product, m
.
In terms of efficiency, when inserting into Q
, the code performs linear search from index 0
. Performing binary search instead of this linear search may or may not make things faster for larger values of a
and b
.
Also to further optimize the code, we can store m
alongside (a, b)
in Q
so that we do not have to calculate a * b
many times. Also using the 1D bucket structure with m
as the key to implement Q
would be interesting.
#!/usr/bin/python
def insert_into_Q((a, b), Q):
if (a == 0) or (b == 0):
return
pos = 0
for (x, y) in Q:
if (x == a) and (y == b):
return
if x * y < a * b:
break
pos = pos + 1
Q.insert(pos, (a, b))
def main(a, b):
Q = [(a, b)]
L = []
while True:
if len(Q) == 0:
break
(a, b) = Q.pop(0)
L.append((a, b)) # Replace this with C(a * b) and break if satisfied.
a1 = a - 1
b1 = b - 1
if (a == b):
insert_into_Q((a1, b), Q)
else:
insert_into_Q((a1, b), Q)
insert_into_Q((a, b1), Q)
print(L)
if __name__ == "__main__":
main(5, 5)
Provided that C(m)
is so magical that you cannot use any better technique to find your solution directly and thus you really need to traverse all a*b
in decreasing order, this is what I would do:
Initialize a max-heap with all pairs (a, b)
such that a = b
. This means that the heap contains (0, 0), (1, 1), ... , (1.000.000, 1.000.000)
. The heap should be based on the a * b
value.
Now continuously:
(a, b)
from the heap.(a, b)
satisfies C(a * b)
. If so, you are done.(a, b-1)
to the heap (provided b > 0
, otherwise do nothing).This is a very simple O(n log n)
time and O(n)
space algorithm, provided that you find the answer quickly (in a few iterations). This of course depends on C
.
If you run into space problems you can of course easily decrease the space complexity by splitting up the problem in a number of subproblems, for instance 2:
(500.000, 500.000), (500.001, 500.001), ... , (1.000.000, 1.000.000)
to the heap and find your best pair (a, b)
.(0, 0), (1, 1), ... (499.999, 499.999)
.Note: this is a test of the function C(m) where m <= some target. It will not work for OP's general situation, but is a side case.
First find the highest number that satisfies C, and then find the pair that matches that high number. Finding the initial target number takes almost no time since its a binary search from 1 to 1E12. Finding the pair that matches is a bit harder, but is still not as bad as factoring.
Code:
public class TargetPractice {
private static final long MAX = 1000000L;
private long target;
public static void main(String[] args) {
Random r = new Random();
for (int i = 0; i < 5; i++) {
TargetPractice tp = new TargetPractice(r.nextInt((int) MAX), r.nextInt((int) MAX));
System.out.println("Trying to find " + tp.target);
System.gc();
long start = System.currentTimeMillis();
long foundTarget = tp.findTarget();
long end = System.currentTimeMillis();
System.out.println("Found " + foundTarget);
System.out.println("Elapsed time " + (end - start) + "\n");
}
}
public TargetPractice(long a, long b) {
target = a * b + 1;
}
private long binSearch() {
double delta = MAX * MAX / 2;
double target = delta;
while (delta != 0) {
if (hit((long) target)) {
target = target + delta / 2;
} else {
target = target - delta / 2;
}
delta = delta / 2;
}
long longTarget = (long) target;
for (int i = 10; i >= -10; i--) {
if (hit(longTarget + i)) {
return longTarget + i;
}
}
return -1;
}
private long findTarget() {
long target = binSearch();
long b = MAX;
while (target / b * b != target || target / b > MAX) {
b--;
if (b == 0 || target / b > MAX) {
b = MAX;
target--;
}
}
System.out.println("Found the pair " + (target/b) + ", " + b);
return target;
}
public boolean hit(long n) {
return n <= target;
}
}
It prints:
Trying to find 210990777760
Found the pair 255976, 824260
Found 210990777760
Elapsed time 5
Trying to find 414698196925
Found the pair 428076, 968749
Found 414698196924
Elapsed time 27Trying to find 75280777586
Found the pair 78673, 956882
Found 75280777586
Elapsed time 1Trying to find 75327435877
Found the pair 82236, 915991
Found 75327435876
Elapsed time 19Trying to find 187413015763
Found the pair 243306, 770277
Found 187413015762
Elapsed time 23
Here's a not particularly efficient way to do this with a heap in Python. This is probably the same thing as the BFS you mentioned, but it's fairly clean. (If someone comes up with a direct algorithm, that would of course be better.)
import heapq # <-this module's API is gross. why no PriorityQueue class?
def pairs_by_reverse_prod(n):
# put n things in heap, since of course i*j > i*(j-1); only do i <= j
# first entry is negative of product, since this is a min heap
to_do = [(-i * n, i, n) for i in xrange(1, n+1)]
heapq.heapify(to_do)
while to_do:
# first elt of heap has the highest product
_, i, j = to_do[0]
yield i, j
# remove it from the heap, replacing if we want to replace
if j > i:
heapq.heapreplace(to_do, (-i * (j-1), i, j-1))
else:
heapq.heappop(to_do)