package com.interview.books.question300; import com.interview.utils.models.Point; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; /** * Created with IntelliJ IDEA. * User: stefanie * Date: 9/23/14 * Time: 2:09 PM * * Solution: to avoid un-useful distance calculation, using p.x to split the binarysearch area into left part and right part. * the closest point pair should be in left part, or right part, or the points which x with 2 * min-dis(left, right) with center of spliter. * splitter * x1 x2 | x3 x4 the min dis of left is d1 and min dis of right is d2, do extra binarysearch on the points lay in splitter +/- min(d1, d2). * * Optimization: * we also could use Y to filter candidate when do extra binarysearch, the candidate should no larger than min(d1, d2) in Y with the min Y in all the candidates. */ public class TQ24_ClosestTwoPoints { static int distanceCount = 0; public static Point[] closest(Point[] points) { Arrays.sort(points, new Comparator<Point>() { @Override public int compare(Point p1, Point p2) { if (p1.x > p2.x) return 1; else if (p1.x < p2.x) return -1; else return 0; } }); Point[] closest = new Point[2]; closest(points, 0, points.length - 1, closest); return closest; } private static double closest(Point[] points, int low, int high, Point[] closest) { //only two point left if (high - low == 1) { closest[0] = points[low]; closest[1] = points[high]; return distance(points[low], points[high]); } //split using the center of endpoint points int split = (points[high].x + points[low].x) / 2; //find the left and right range: (low, left) and (right, high) int left = low; while(points[left + 1].x < split) left++; int right = left + 1; //to avoid single node goes to closest function if(left == low) left++; if(right == high) right--; Point[] closetLeft = new Point[2]; double disLeft = closest(points, low, left, closetLeft); Point[] closetRight = new Point[2]; double disRight = closest(points, right, high, closetRight); //find the min dis and points double disMin = disLeft < disRight ? disLeft : disRight; closest[0] = disLeft < disRight ? closetLeft[0] : closetRight[0]; closest[1] = disLeft < disRight ? closetLeft[1] : closetRight[1]; //find the points lay in splitter +/- min(d1, d2), update the min dis and points if more closer points found List<Integer> candidates = candidate(points, left, right, split, disMin); for (int i = 0; i < candidates.size() && candidates.get(i) <= left; i++) { int p = candidates.get(i); for (int j = i + 1; j < candidates.size(); j++) { //use Y to filter dis candidate larger than disMin //only calculate the dis between left nodes and right nodes int q = candidates.get(j); if(p == q || q < right || Math.abs(points[p].y - points[q].y) > disMin) continue; double ten = distance(points[p], points[q]); if (ten < disMin) { closest[0] = points[p]; closest[1] = points[q]; disMin = ten; } } } return disMin; } private static List<Integer> candidate(Point[] points, int left, int right, int center, double dis) { List<Integer> candidates = new ArrayList<Integer>(); while (left >= 0) { if (center - points[left].x <= dis) { if(!candidates.contains(left)) candidates.add(left); left--; } else break; } while (right < points.length) { if (points[right].x - center <= dis) { if(!candidates.contains(right)) candidates.add(right); right++; } else break; } return candidates; } public static Point[] correct(Point[] points) { Point[] closest = new Point[2]; double min = Integer.MAX_VALUE; for (int i = 0; i < points.length; i++) { for (int j = i + 1; j < points.length; j++) { double ten = distance(points[i], points[j]); if (ten < min) { closest[0] = points[i]; closest[1] = points[j]; min = ten; } } } return closest; } public static double distance(Point i, Point j) { distanceCount++; return Math.sqrt(Math.pow((i.y - j.y), 2) + Math.pow((i.x - j.x), 2)); } }