(Original problem description)
Pair of points
You are given the following
An integer N
A 2D array of length N denoting the points in the 2D coordinate system, that is (x, y)
Task
Determine the number of unordered pairs (i, j) or (j, i) and i != j such that
The straight line connecting the points (A[i][1], A[i][2]) and (A[j][1], A[j][2]) passes through (0, 0)
(Context, this was a coding problem on hacker earth site and I did solve it (bruteforce) method)
My code:
def find_pairs(array, size):
li = []
for i in range(size):
for j in range(size):
if (i, j) not in li and (j, i) not in li:
if ((array[i][1] * (array[j][0] - array[i][0])) == ((array[i][0] * (array[j][1] - array[i][1]))):
li.append((i,j))
return len(li)
The math the code uses is, given two points (x1, y1) and (x2, y2), their line passes through the origin if they satisfy the equation (x1 * (y2 - y1)) = (y1 * (x2 - x1))
This code passed half the test cases (which were testing for correct answer) but failed the remaining which had time constraint. I tried to use itertools.combinations but it exceeded the memory limit
Is there any way to write a program with less than N2 time complexity?
CodePudding user response:
Put tangent (slope) of line origin-to-point into Counter
(in general - dictionary with value as counter for old Python without this class). Make separate counter for points with x=0 (vertical line).
After putting all slopes into the map, retieve number of pairs for every slope - n
points with the same slope give n*(n-1)/2
pairs
Linear complexity.
from collections import Counter
pts = [[1,0],[2,2],[-1,-1],[4,4],[0,2],[-2,0]]
vert = 0
cntr = Counter()
for p in pts:
if p[0]:
cntr[p[1]/p[0]] = 1
else:
vert = 1
res = vert * (vert - 1) // 2
for v in cntr.values():
res = v*(v-1) // 2
print(res) # 4 pairs
Update: accounts for (0,0):
from collections import Counter
pts = [[1,0],[2,2],[-1,-1],[4,4],[0,2],[-2,0],[0,0],[0,0]]
vert = 0
zeros = 0
cntr = Counter()
for p in pts:
if p[0]:
cntr[p[1]/p[0]] = 1
else:
if p[1]:
vert = 1
else:
zeros = 1
res = vert * (vert - 1) // 2
for v in cntr.values():
res = v*(v-1) // 2
res = (len(pts) - zeros) * zeros zeros*(zeros-1)//2
print(res) //17 pairs
Potential pitfall - float number comparison might give wrong results for some pairs.
If points are integers, it is possible to use integer tuple ( sign(x)*y/gcd(y,x), abs(x)/gcd(x,y) )
as key
CodePudding user response:
One optimization we can make in your code is not to check previously traversed co-ordinates:
def find_pairs(array, size):
count = 0
for i in range(size - 1):
for j in range(i 1, size):
if ((array[i][1] * (array[j][0] - array[i][0])) == ((array[i][0] * (array[j][1] - array[i][1]))):
count = 1
return count
CodePudding user response:
If using itertools.combinations
exceeded your memory limit then you used it incorrectly. The functions in the itertools
library are generally quite memory efficient because they work with iterators rather than creating complete lists in memory.
num_pairs = 0
for i, j in itertools.combinations(array, 2):
if i_j_are_valid_points(): # your formula here
num_pairs = 2 # (i, j) and (j, i) are both valid
Note that this is still O(N^2) - you can't find all combinations of pairs in less time. But it will run over twice as fast as your solution since: 1) it doesn't check both (i, j) and (j, i) redundantly; 2) it doesn't need to traverse the list of already found solutions.
CodePudding user response:
I suspect you can do this in O(NlogN) (merge sort) time, at least in theory. If you convert to polar coordinates, then you're looking for two points (r_1, theta) and (r_2, theta pi) so you can just sort them and then process from theta in [0, pi), and compare with those from theta in [pi, 2pi). If you use floating point numbers though, you'll need to be careful how you compare the two values because floating point values are only stored approximately.