Dealing with Union-Find algorithms with a lot of objects - java
I have a problem with (not anymore with stackoverflow (hehe)) Find algorithm when trying to implement UnionFind structure algorithm with path-compression.
I have standard array of ints, array can get pretty big -> it works fine until 60.000.000 elements.
My Union function looks like this:
public void unite(int p, int q) {
if(p >= 0 && p < id.length && q >= 0 && q < id.length){
if (isInSameSet(p, q)) return;
id[find(p)] = find(q);
stevilo--;
}
}
My isInSameSet looks like this:
public boolean isInSameSet(int p, int q) {
if(p >= 0 && p < id.length && q >= 0 && q < id.length)
return find(p) == find(q);
return false;
}
I have tried iterative way in Find:
public int find(int i) {
while (i != id[i]){
id[i] = id[id[i]];
i = id[i];
}
return i;
}
and tail-recrusion:
public int find(int i) {
int p = id[i];
if (i == p) {
return i;
}
return id[i] = find(p);
}
Is there anything I missed in my code? Is there any other approach to this kind of problems?
#edit: Adding constructor to code:
public UnionFind(int N) {
stevilo = N;
id = new int[N];
for(int i = 0; i < N; i++){
id[i] = i;
}
#edit2 (better explanation and new findings):
The problem is not in stackoverflow anymore for less then 60.000.000 elements, which is more then enough for solving my problems.
I'm calling test Unions like this:
for(i=0;i<id.length-1;i++)
unite(i,i+1)
so the ending pairs are like this:
0:1, 1:2, 2:3, 3:4,..
which is only example of least optimal option for testing means only :)
Then I check if representative of 0 is last element in table (99 for 100 elements) and it works.
Problem is, that my algorithm works only if initial elements are each in their own union (0:0, 1:1, 2:2, 3:3). If I have different Unions already set up (0:2, 1:6, 2:1, 3:5, ...) my testing algorithm stops working.
I have narrow it down to a problem in Find function, probably something to do with path compression
id[i] = id[id[i]].
One small optimization would be to get rid of isInSameSet...
public void unite(int p, int q) {
if(p >= 0 && p < id.length && q >= 0 && q < id.length){
int rootp = find(p);
int rootq = find(q);
if (rootp==rootq) return;
id[rootp] = rootq;
stevilo--;
}
}
Union-Find data structures typically include TWO different optimizations. One is path compression. You have that.
But the other optimization happens during a Union, where you carefully choose which of the two roots to make a child of the other, usually via Union-By-Rank or Union-By-Size. With that optimization, your trees should never be deep enough to get a stack overflow. However, that optimization seems to be missing from your unite function.
I once wrote an algorithm for UnionFind, and its time complexity is O(log*(n)). Thats iterative logarithm of n. The algorithm compresses the path of the tree as it keeps on connecting the nodes to gain efficiency. I find it very efficient, though I haven't practically tested it against huge array size. Here's the code:
public class UnionFind
{
private int[] id;
public UnionFind(int capacity)
{
id = new int[capacity];
for (int i = 0; i < capacity; i++)
{
id[i] = i;
}
}
public boolean isConnected(int p, int q)
{
return root(p) == root(q);
}
public void connect(int p, int q)
{
if (isConnected(p, q))
{
return;
}
id[root(p)] = root(q);
}
private int root(int p)
{
int temp = p;
if (p != id[p] && id[id[p]] != id[p])
{
while (p != id[p])
{
p = id[p];
}
id[temp] = id[p];
}
return id[p];
}
}
Related
Implementing Union-Find Algorithm for Kruskal's Algorithm to find Minimum Spanning Tree in Java
I am trying to solve the following Leetcode problem (https://leetcode.com/problems/connecting-cities-with-minimum-cost), and my approach is to figure out the total weight of the minimum spanning tree (MST) from the input graph using Kruskal's Algorithm using the Union-Find data structure. However, my code online passes 51/63 of the test cases, returning the incorrect result on the following test case, which is too hard to debug, since the input graph is too large. 50 [[2,1,22135],[3,1,13746],[4,3,37060],[5,2,48513],[6,3,49607],[7,1,97197],[8,2,95909],[9,2,82668],[10,2,48372],[11,4,17775],[12,2,6017],[13,1,51409],[14,2,12884],[15,7,98902],[16,14,52361],[17,8,11588],[18,12,86814],[19,17,49581],[20,4,41808],[21,11,77039],[22,10,80279],[23,16,61659],[24,12,89390],[25,24,10042],[26,12,78278],[27,15,30756],[28,6,2883],[29,8,3478],[30,7,29321],[31,12,47542],[32,20,35806],[33,3,26531],[34,12,16321],[35,27,82484],[36,7,55920],[37,24,21253],[38,23,90537],[39,7,83795],[40,36,70353],[41,34,76983],[42,14,63416],[43,15,39590],[44,9,86794],[45,3,31968],[46,19,32695],[47,17,40287],[48,1,27993],[49,12,86349],[50,11,52080],[17,27,65829],[42,45,87517],[14,23,96130],[5,50,3601],[10,17,2017],[26,44,4118],[26,29,93146],[1,9,56934],[22,43,5984],[3,22,13404],[13,28,66475],[11,14,93296],[16,44,71637],[7,37,88398],[7,29,56056],[2,34,79170],[40,44,55496],[35,46,14494],[32,34,25143],[28,36,59961],[10,49,58317],[8,38,33783],[8,28,19762],[34,41,69590],[27,37,26831],[15,23,53060],[5,11,7570],[20,42,98814],[18,34,96014],[13,43,94702],[1,46,18873],[44,45,43666],[22,40,69729],[4,25,28548],[8,46,19305],[15,22,39749],[33,48,43826],[14,15,38867],[13,22,56073],[3,46,51377],[13,15,73530],[6,36,67511],[27,38,76774],[6,21,21673],[28,49,72219],[40,50,9568],[31,37,66173],[14,29,93641],[4,40,87301],[18,46,41318],[2,8,25717],[1,7,3006],[9,22,85003],[14,45,33961],[18,28,56248],[1,31,10007],[3,24,23971],[6,28,24448],[35,39,87474],[10,50,3371],[7,18,26351],[19,41,86238],[3,8,73207],[11,34,75438],[3,47,35394],[27,32,69991],[6,40,87955],[2,18,85693],[5,37,50456],[8,20,59182],[16,38,58363],[9,39,58494],[39,43,73017],[10,15,88526],[16,23,48361],[4,28,59995],[2,3,66426],[6,17,29387],[15,38,80738],[12,43,63014],[9,11,90635],[12,20,36051],[13,25,1515],[32,40,72665],[10,40,85644],[13,40,70642],[12,24,88771],[14,46,79583],[30,49,45432],[21,34,95097],[25,48,96934],[2,35,79611],[9,26,71147],[11,37,57109],[35,36,67266],[42,43,15913],[3,30,44704],[4,32,46266],[5,10,94508],[31,39,45742],[12,25,56618],[10,45,79396],[15,28,78005],[19,32,94010],[36,46,4417],[6,35,7762],[10,13,12161],[49,50,60013],[20,23,6891],[9,50,63893],[35,43,74832],[10,24,3562],[6,8,47831],[29,32,82689],[7,47,71961],[14,41,82402],[20,33,38732],[16,26,24131],[17,34,96267],[21,46,81067],[19,47,41426],[13,24,68768],[1,25,78243],[2,27,77645],[11,25,96335],[31,45,30726],[43,44,34801],[3,42,22953],[12,23,34898],[37,43,32324],[18,44,18539],[8,13,59737],[28,37,67994],[13,14,25013],[22,41,25671],[1,6,57657],[8,11,83932],[42,48,24122],[4,15,851],[9,29,70508],[7,32,53629],[3,4,34945],[2,32,64478],[7,30,75022],[14,19,55721],[20,22,84838],[22,25,6103],[8,49,11497],[11,32,22278],[35,44,56616],[12,49,18681],[18,43,56358],[24,43,13360],[24,47,59846],[28,43,36311],[17,25,63309],[1,14,30207],[39,48,22241],[13,26,94146],[4,33,62994],[40,48,32450],[8,19,8063],[20,29,56772],[10,27,21224],[24,30,40328],[44,46,48426],[22,45,39752],[6,43,96892],[2,30,73566],[26,36,43360],[34,36,51956],[18,20,5710],[7,22,72496],[3,39,9207],[15,30,39474],[11,35,82661],[12,50,84860],[14,26,25992],[16,39,33166],[25,41,11721],[19,40,68623],[27,28,98119],[19,43,3644],[8,16,84611],[33,42,52972],[29,36,60307],[9,36,44224],[9,48,89857],[25,26,21705],[29,33,12562],[5,34,32209],[9,16,26285],[22,37,80956],[18,35,51968],[37,49,36399],[18,42,37774],[1,30,24687],[23,43,55470],[6,47,69677],[21,39,6826],[15,24,38561]] I'm having trouble understanding why my code will fail a test case, since I believe I am implementing the steps of Kruskal's Algorithm propertly: Sorting the connections in increasing order of weight. Building the MST by going through each connection in the sorted list and selecting that connection if it does not result in a cycle in the MST. Below is my Java code: class UnionFind { // parents[i] = parent node of node i. // If a node is the root node of a component, we define its parent // to be itself. int[] parents; public UnionFind(int n) { this.parents = new int[n]; for (int i = 0; i < n; i++) { this.parents[i] = i; } } // Merges two nodes into the same component. public void union(int node1, int node2) { int node1Component = find(node1); int node2Component = find(node2); this.parents[node1Component] = node2Component; } // Returns the component that a node is in. public int find(int node) { while (this.parents[node] != node) { node = this.parents[node]; } return node; } } class Solution { public int minimumCost(int n, int[][] connections) { UnionFind uf = new UnionFind(n + 1); // Sort edges by increasing cost. Arrays.sort(connections, new Comparator<int[]>() { #Override public int compare(final int[] a1, final int[] a2) { return a1[2] - a2[2]; } }); int edgeCount = 0; int connectionIndex = 0; int weight = 0; // Greedy algorithm: Choose the edge with the smallest weight // which does not form a cycle. We know that an edge between // two nodes will result in a cycle if those nodes are already // in the same component. for (int i = 0; i < connections.length; i++) { int[] connection = connections[i]; int nodeAComponent = uf.find(connection[0]); int nodeBComponent = uf.find(connection[1]); if (nodeAComponent != nodeBComponent) { weight += connection[2]; edgeCount++; } if (edgeCount == n - 1) { break; } } // MST, by definition, must have (n - 1) edges. if (edgeCount == n - 1) { return weight; } return -1; } }
As #geobreze stated, I forgot to unite the components (disjoint sets) of node A and node B. Below is the corrected code: if (nodeAComponent != nodeBComponent) { uf.union(nodeAComponent, nodeBComponent); weight += connection[2]; edgeCount++; }
Find if 2 trees have similar leaves(from left to right)?
As far as my logic goes I'm using two different arrays to store all the leafs and then compare those arrays to see if the leaves are indeed the same, but my test cases are failing (for eg. [3,5,1,6,2,9,8,null,null,7,4] [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8]). Thanks in advance! ''' class Solution { static int array1[] = new int[50]; static int array2[] = new int[50]; static int q = 0; static int r = 0; public boolean compareLeaves(int arr1[], int arr2[]) { for(int i = 0; i <array1.length ;i ++) { if(array1[i] != array2[i]) { return false; } } return true; } public boolean leafSimilar(TreeNode root1, TreeNode root2) { if(root1 == null || root2 == null) { return false; } if(root1.left == null && root1.right == null) { array1[q] =root1.val ; q++; } if(root2.left == null && root2.right == null) { array2[r] =root2.val ; r++; } leafSimilar(root1.left,root2.left); leafSimilar(root1.right,root2.right); return compareLeaves(array1,array2); } } '''
If the arrays have different lengths but agree on the first array1.length elements, I believe you deem them equal (return true). You probably need to use q and r for determining whether the element counts are the same and how many elements to compare. If both roots are null, I would expect that the trees should be considered equal, but you return false. Even if root1 == null, you should still pick up the leaves from root2 and vice versa. I think you should do in-order traversal, that is, call leafSimilar(root1.left,root2.left) before you look at root1.val and root2.val. It may be that it doesn’t matter since the val is only considered for leaves, but I find it hard to be 100 % sure. I may have missed something. Using two different arrays to store all the leaves should be a sound strategy. I think it will be easier if you process each tree separately, not both trees at once.
your program will fail for test case: tree1 : 1 / \ null null tree2: 2 / \ 1 null clearly both trees have only one leaf node 1 but your code will fail because you are iterating on both of them identically. you should iterate on them separately and store leaf node in the array's. And in the end check if both array have same elements no matter the order. tree1 : 1 / \ 2 3 tree2: 1 / \ 3 2 Above two trees have same leaves, I have Updated functions to implement it correctly. public int leafSimilar(TreeNode root, int arr[], int l) { if(root == null) { return l; } if(root.left == null && root.right == null) { arr[l] =root.val ; l+=1; return l; } l = leafSimilar(root.left, l); l = leafSimilar(root.right, l); return l; } public boolean compareLeaves(int arr1[], int arr2[], int l, int r) { if( l != r ) return false; for(int i = 0; i <l ;i ++) { boolean flag = true; for(int j = 0; j <r ;j ++) { if(arr1[i] == arr2[j]) { flag = false; break; } } if( flag) return false; } return true; } int l = leafSimilar(root1, arr1, 0); int r = leafSimilar(root2, arr2, 0); compareLeaves(arr1, arr2, l, r); Also above function will fail if tree can have duplicate node. Update the compare function to count frequency of all nodes in array one and then match it with frequency of nodes in array2. It will handle duplicate nodes.
The below line of code is suggesting both the tree to follow the same path thereby, ignoring the leaves in one of the tree1 or tree2. if(root1 == null || root2 == null) { return false; } It's better to traverse both the tree one by one. And keep on storing the leaves. public static boolean compare() { for(int i = 0; i <array1.length ;i ++) { if(array1[i] != array2[i]) { return false; } } return true; } public void isSimilar(Node root, int flag) { if(root==null) return; if(root.left == null && root.right == null) { if(flag==1) { array1[q] =root.val ; q++; } else { array2[r] =root.val ; r++; } } isSimilar(root.left,flag); isSimilar(root.right,flag); } You have to pass a flag variable to point which array to populate. For ex, here 0 refer to tree1 and populate array1, and 1 refer to tree2 and populate array2 : isSimilar(root1, 0); isSimilar(root2, 1);
For k collections all of which have length = N, finding common elements with a O((k-1)*N)
I am supposed to write a code which is supposed to find the common elements existing in k collections of N-elements efficiently. All collections are sorted and they may have various sizes, but let's assume same sizes for the sake of simplicity here. Only thing that counts is the comparisons between elements; that should be less than O((k-1)*N). I have developed the below code, but in case of mentioned scenario the number of comparisons is about (k-1)NN Appreciate the help in advance. //Arrays are sorted and the shortest array is chosen as the query automatically boolean com; loop1: for (int i = 0; i < QuetyList.length; ++i) { com = false; loop2: for (int k = 0; k < OtherLists.length; ++k) { com = false; loop3: for (int y = 0; y < OtherLists[k].size(); ++y) { ++comparisons; if (QueryList[i].compareTo(OtherLists[k][y]) == 0) { com = true; break loop3; } ++comparisons; if (QueryList[i].compareTo(OtherLists[k][y]) < 0) { break; } } if (com == false) { break; } } if (com == true) { commons.add(QueryList[i]); } } Sample test Comparable [] QuetyList = {200,200,200,200}; Comparable [] collection2 = {2,10,50,200}; Comparable [] collection3 = {2,10,40,200}; Comparable [][] OtherLists = {collection2,collection3}; This is for a homework. There is a chance you may have crossed sometime in your education. Thanks in advance.
The basic idea is to keep an index on every list you have, and only advance this index when the value at the index is the smallest among all the lists. I can't see if it's doable for k lists at once, but it's certainly doable 2 lists at a time, each should take N comparisons, which should give you O(k * N) (k-1 runs of N comparison). Something like: public Comparable[] common(Comparable[] a, Comparable[] b) { // List is more flexible, but the toArray at the end is a bit costly. You can probably figure a better way of doing this. List<Comparable> res = new ArrayList<>(); int indexA = 0; int indexB = 0; while (indexA < a.length && indexB < b.length) { // Exercice for the reader: replace with compareTo calls if (a[indexA] == b[indexB]) { // Common item! res.add(a[indexA]); indexA++; indexB++; } else if (a[indexA] < b[indexB]) { // item in A is smaller, try the next indexA++; } else { indexB++; } } return res.toArray(new Comparable[0]); } From this, you can group your lists 2 by 2 until only one list remains.
How to make a range tree implementation thread safe
I've implemented a range tree which supports updates in the form of incrementing or decrementing the count of a specific value. It can also query the number of values lower or equal to the value provided. The range tree has been tested to work in a single threaded environment, however I would like to know how to modify the implementation such that it can be updated and queried concurrently. I know a simple solution would be to synchronise methods that access this tree, but I would like to know if there are ways to make RangeTree thread safe by itself with minimal affect on performance. public class RangeTree { public static final int ROOT_NODE = 0; private int[] count; private int[] min; private int[] max; private int levels; private int lastLevelSize; public RangeTree(int maxValue) { levels = 1; lastLevelSize = 1; while (lastLevelSize <= maxValue) { levels++; lastLevelSize = lastLevelSize << 1; } int alloc = lastLevelSize * 2; count = new int[alloc]; min = new int[alloc]; max = new int[alloc]; int step = lastLevelSize; int pointer = ROOT_NODE; for (int i = 0; i < levels; i++) { int current = 0; while (current < lastLevelSize) { min[pointer] = current; max[pointer] = current + step - 1; current += step; pointer++; } step = step >> 1; } } public void register(int value) { int index = lastLevelSize - 1 + value; count[index]++; walkAndRefresh(index); } public void unregister(int value) { int index = lastLevelSize - 1 + value; count[index]--; walkAndRefresh(index); } private void walkAndRefresh(int node) { int currentNode = node; while (currentNode != ROOT_NODE) { currentNode = (currentNode - 1) >> 1; count[currentNode] = count[currentNode * 2 + 1] + count[currentNode * 2 + 2]; } } public int countLesserOrEq(int value) { return countLesserOrEq0(value, ROOT_NODE); } private int countLesserOrEq0(int value, int node) { if (max[node] <= value) { return count[node]; } else if (min[node] > value) { return 0; } return countLesserOrEq0(value, node * 2 + 1) + countLesserOrEq0(value, node * 2 + 2); } }
Louis Wasserman is right, this is a difficult question. But it may have simple solution. Depending on your updates/reads ratio and the contention for the data, it may be useful to use ReadWriteLock instead of synchronized. Another solution which may be efficient in some cases (depends on your workload) is to copy whole RangeTree object before update and then switch the reference to 'actual' RangeTree. Like it is done in CopyOnWriteArrayList. But this also violates atomic consistency agreement and leads us to eventual consistency.
Find closest value in an ordered list
I am wondering how you would write a simple java method finding the closest Integer to a given value in a sorted Integer list. Here is my first attempt: public class Closest { private static List<Integer> integers = new ArrayList<Integer>(); static { for (int i = 0; i <= 10; i++) { integers.add(Integer.valueOf(i * 10)); } } public static void main(String[] args) { Integer closest = null; Integer arg = Integer.valueOf(args[0]); int index = Collections.binarySearch( integers, arg); if (index < 0) /*arg doesn't exist in integers*/ { index = -index - 1; if (index == integers.size()) { closest = integers.get(index - 1); } else if (index == 0) { closest = integers.get(0); } else { int previousDate = integers.get(index - 1); int nextDate = integers.get(index); if (arg - previousDate < nextDate - arg) { closest = previousDate; } else { closest = nextDate; } } } else /*arg exists in integers*/ { closest = integers.get(index); } System.out.println("The closest Integer to " + arg + " in " + integers + " is " + closest); } } What do you think about this solution ? I am sure there is a cleaner way to do this job. Maybe such method exists somewhere in the Java libraries and I missed it ?
try this little method: public int closest(int of, List<Integer> in) { int min = Integer.MAX_VALUE; int closest = of; for (int v : in) { final int diff = Math.abs(v - of); if (diff < min) { min = diff; closest = v; } } return closest; } some testcases: private final static List<Integer> list = Arrays.asList(10, 20, 30, 40, 50); #Test public void closestOf21() { assertThat(closest(21, list), is(20)); } #Test public void closestOf19() { assertThat(closest(19, list), is(20)); } #Test public void closestOf20() { assertThat(closest(20, list), is(20)); }
Kotlin is so helpful fun List<Int>.closestValue(value: Int) = minBy { abs(value - it) } val values = listOf(1, 8, 4, -6) println(values.closestValue(-7)) // -6 println(values.closestValue(2)) // 1 println(values.closestValue(7)) // 8 List doesn't need to be sorted BTW Edit: since kotlin 1.4, minBy is deprecated. Prefer minByOrNull #Deprecated("Use minByOrNull instead.", ReplaceWith("this.minByOrNull(selector)")) #DeprecatedSinceKotlin(warningSince = "1.4")
A solution without binary search (takes advantage of list being sorted): public int closest(int value, int[] sorted) { if(value < sorted[0]) return sorted[0]; int i = 1; for( ; i < sorted.length && value > sorted[i] ; i++); if(i >= sorted.length) return sorted[sorted.length - 1]; return Math.abs(value - sorted[i]) < Math.abs(value - sorted[i-1]) ? sorted[i] : sorted[i-1]; }
To solve the problem, I'd extend the Comparable Interface by a distanceTo method. The implementation of distanceTo returns a double value that represents the intended distance and which is compatible with the result of the compareTo implementation. The following example illustrates the idea with just apples. You can exchange diameter by weight, volume or sweetness. The bag will always return the 'closest' apple (most similiar in size, wight or taste) public interface ExtComparable<T> extends Comparable<T> { public double distanceTo(T other); } public class Apple implements Comparable<Apple> { private Double diameter; public Apple(double diameter) { this.diameter = diameter; } public double distanceTo(Apple o) { return diameter - o.diameter; } public int compareTo(Apple o) { return (int) Math.signum(distanceTo(o)); } } public class AppleBag { private List<Apple> bag = new ArrayList<Apple>(); public addApples(Apple...apples){ bag.addAll(Arrays.asList(apples)); Collections.sort(bag); } public removeApples(Apple...apples){ bag.removeAll(Arrays.asList(apples)); } public Apple getClosest(Apple apple) { Apple closest = null; boolean appleIsInBag = bag.contains(apple); if (!appleIsInBag) { bag.addApples(apple); } int appleIndex = bag.indexOf(apple); if (appleIndex = 0) { closest = bag.get(1); } else if(appleIndex = bag.size()-1) { closest = bag.get(bag.size()-2); } else { double absDistToPrev = Math.abs(apple.distanceTo(bag.get(appleIndex-1)); double absDistToNext = Math.abs(apple.distanceTo(bag.get(appleIndex+1)); closest = bag.get(absDistToNext < absDistToPrev ? next : previous); } if (!appleIsInBag) { bag.removeApples(apple); } return closest; } }
Certainly you can simply use a for loop to go through the and keep track of the difference between the value you are on and the value. It would look cleaner, but be much slower. See: Finding closest match in collection of numbers
I think what you have is about the simplest and most efficient way to do it. Finding the "closest" item in a sorted list isn't something that is commonly encountered in programming (you typically look for the one that is bigger, or the one that is smaller). The problem only makes sense for numeric types, so is not very generalizable, and thus it would be unusual to have a library function for it.
Not tested int[] randomArray; // your array you want to find the closest int theValue; // value the closest should be near to for (int i = 0; i < randomArray.length; i++) { int compareValue = randomArray[i]; randomArray[i] -= theValue; } int indexOfClosest = 0; for (int i = 1; i < randomArray.length; i++) { int compareValue = randomArray[i]; if(Math.abs(randomArray[indexOfClosest] > Math.abs(randomArray[i]){ indexOfClosest = i; } }
I think your answer is probably the most efficient way to return a single result. However, the problem with your approach is that there are 0 (if there is no list), 1, or 2 possible solutions. It's when you have two possible solutions to a function that your problems really start: What if this is not the final answer, but only the first in a series of steps to determine an optimal course of action, and the answer that you didn't return would have provided a better solution? The only correct thing to do would be to consider both answers and compare the results of further processing only at the end. Think of the square root function as a somewhat analogous problem to this.
If you're not massively concerned on performance (given that the set is searched twice), I think using a Navigable set leads to clearer code: public class Closest { private static NavigableSet<Integer> integers = new TreeSet<Integer>(); static { for (int i = 0; i <= 10; i++) { integers.add(Integer.valueOf(i * 10)); } } public static void main(String[] args) { final Integer arg = Integer.valueOf(args[0]); final Integer lower = integers.lower(arg); final Integer higher = integers.higher(arg); final Integer closest; if (lower != null) { if (higher != null) closest = (higher - arg > arg - lower) ? lower : higher; else closest = lower; } else closest = higher; System.out.println("The closest Integer to " + arg + " in " + integers + " is " + closest); } }
Your solution appears to be asymptotically optimal. It might be slightly faster (though probably less maintainable) if it used Math.min/max. A good JIT likely has intrinsics that make these fast. int index = Collections.binarySearch(integers, arg); if (index < 0) { int previousDate = integers.get(Math.max(0, -index - 2)); int nextDate = integers.get(Math.min(integers.size() - 1, -index - 1)); closest = arg - previousDate < nextDate - arg ? previousDate : nextDate; } else { closest = integers.get(index); }
Probably a bit late, but this WILL work, this is a data structure binary search: Kotlin: fun binarySearch(list: List<Int>, valueToCompare: Int): Int { var central: Int var initialPosition = 0 var lastPosition: Int var centralValue: Int lastPosition = list.size - 1 while (initialPosition <= lastPosition) { central = (initialPosition + lastPosition) / 2 //Central index centralValue = list[central] //Central index value when { valueToCompare == centralValue -> { return centralValue //found; returns position } valueToCompare < centralValue -> { lastPosition = central - 1 //position changes to the previous index } else -> { initialPosition = central + 1 //position changes to next index } } } return -1 //element not found } Java: public int binarySearch(int list[], int valueToCompare) { int central; int centralValue; int initialPosition = 0; int lastPosition = list . length -1; while (initialPosition <= lastPosition) { central = (initialPosition + lastPosition) / 2; //central index centralValue = list[central]; //central index value if (valueToCompare == centralValue) { return centralValue; //element found; returns position } else if (valueToCompare < centralValue) { lastPosition = central - 1; //Position changes to the previous index } else { initialPosition = central + 1; //Position changes to the next index } return -1; //element not found } } I hope this helps, happy coding.