The first attempt that I made build a tree from the ranges.
You'd first find the root of a subtree which contained all the solutions to the query. This is pretty easy. However: that subtree contains some values outside of the solution set.
So to find the lowest number, you'd traverse the tree twice (from the root down the left side of the tree and again from the root down the right side). At each point in those traversals, you'd check to see if you'd found a solution lower than your previous solution.
When done, the lowest solution within that set would be the lowest.
Since checking each node's solutions required a binary search on the list of indexes within that subtree, you'd end up running at O(ln(n)^2) which is too slow.
The improvement on that comes from the fact that this would be easy if we were always looking for the first element in the array. Then you don't need to do a binary search, you just get the first element.
To get to that place, you end up building n trees.
tree[0] has all values in the initializaing array
tree[1] has all but the first.
tree[2] has all but the first and second.
So each of those trees just contains the single best solution.
So the search runs in O(ln(n)).
In the example code, took a liberty with building the tree by not implementing custom data structures.
You build an array of nodes, make a copy of that array and sort one of the copies. This runs in O(n ln (n)). The other copy is used to find the position in the sorted array so you can delete an element each time you build another tree.
Of course it is possible to delete a node in a linked list in constant time, but since you only have a reference to the object, I suspect that the java implementation has to search the linked list for the item to be deleted, so building the trees probably takes O(n^2) in this example. Easy to fix though.
public class ArraySearcher {
public class SegmentNode implements Comparable<SegmentNode> {
int min;
int max;
SegmentNode root;
SegmentNode left;
SegmentNode right;
int lowIndex;
public int compareTo(final SegmentNode obj)
{
return this.min - obj.min;
}
public boolean contains(final int n)
{
if ((n > this.min) && (n < this.max)) {
return true;
} else {
return false;
}
}
/**
* Given the root of a tree, this method will find the center node. The
* center node is defined as the first node in the tree who's left and
* right nodes either contain solutions or are null.
*
* If no node can be found which contains solution, this will return
* null.
*
* @param a
* @param b
* @return
*/
public SegmentNode findCenter(final int a, final int b)
{
SegmentNode currentNode = this;
while (true) {
/*
* first check to see if no solution can be found in this node.
* There is no solution if both nodes are
*/
if ((a < currentNode.min && b < currentNode.min)
|| (a > currentNode.max && b > currentNode.max)) {
return null;
}
/*
* Now check to see if this is the center.
*/
if (((currentNode.left == null) || (a < currentNode.left.max))
&& ((currentNode.right == null) || (b > currentNode.right.min))) {
// this is the center, return it.
return currentNode;
} else if ((currentNode.left == null)
|| (a < currentNode.left.max)) {
// no solution on one side, is it in the left?
currentNode = currentNode.left;
} else {
currentNode = currentNode.right;
}
}
}
public SegmentNode findLeft(final int seekMin)
{
/*
* keep going left until
*/
if ((this.min > seekMin)) {
return this;
} else {
if (this.right == null) {
return null;
}
return this.right.findLeft(seekMin);
}
}
/**
* This method can be called on the center node. it traverses left
*
* @param a
* @param b
* @return
*/
public Integer findLeftmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.left;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left
// if they are greater than N
if ((null != current.left) && (current.left.min > n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.min > n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.max > n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.leftSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
public SegmentNode findMatch(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if ((this.min > seekMin) && (this.left != null)) {
return this.left.findMatch(seekMin, seekMax);
} else {
if (this.right == null) {
return null;
}
return this.right.findMatch(seekMin, seekMax);
}
}
public SegmentNode findMatchRight(final int seekMin, final int seekMax)
{
if ((this.min > seekMin) && (this.max < seekMax)) {
return this;
} else if (this.max < seekMax) {
if (this.right == null) {
return null;
}
return this.right.findMatchRight(seekMin, seekMax);
} else {
if (this.left == null) {
return null;
}
return this.left.findMatchRight(seekMin, seekMax);
}
}
/**
* Search for the first number in the tree which is lower than n.
*
* @param n
* @return
*/
public Integer findRightmostLowestIndex(final int n)
{
if (null == this.left) {
return null;
}
int lowest = Integer.MAX_VALUE;
SegmentNode current = this.right;
// update the lowest with the right node if it is greater
// than the current node.
while (true) {
// collect the best value from the right and/or left //this.max
// < b
// if they are greater than N
if ((null != current.left) && (current.left.max < n)) {
lowest = current.left.lowIndex(lowest);
}
if ((null != current.right) && (current.right.max < n)) {
lowest = current.right.lowIndex(lowest);
}
if ((null == current.left) && (null == current.right)
&& (current.min < n)) {
lowest = current.lowIndex(lowest);
}
// quit when we've gone as far left as we can
int seek = current.rightSeek(n);
if (seek == 0) {
break;
} else if (seek < 0) {
current = current.left;
} else {
current = current.right;
}
}
if (lowest == Integer.MAX_VALUE) {
return null;
} else {
return new Integer(lowest);
}
}
/**
*
* @param seekMin
* @param seekMax
* @return
*/
public SegmentNode findSegmentRoot(final int seekMin, final int seekMax)
{
return null;
}
public int leftSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
// } else if ((null != this.left) && (this.left.max > n)) {
} else if ((null != this.left) && ((n < this.left.max))
|| (this.left.contains(n))) {
return -1;
// } else if ((null != this.right) && (this.right.min <= n)) {
} else if ((null != this.right) && ((n >= this.right.min))) {
return +1;
} else {
return 0;
}
}
public int rightSeek(final int n)
{
if ((null == this.left) && (null == this.right)) {
return 0;
} else if ((null != this.left) && (this.left.max >= n)) {
return -1;
} else if ((null != this.right) && (this.right.min < n)) {
return +1;
} else {
return 0;
}
}
@Override
public String toString()
{
StringBuilder value = new StringBuilder();
if (null != this.left) {
value.append("{ { " + this.left.min + ", " + this.left.max
+ "} }, ");
} else {
value.append("{ " + this.min + " }, ");
}
if (null != this.right) {
value.append("{ { " + this.right.min + ", " + this.right.max
+ "} }");
} else {
value.append("{ " + this.max + " }, ");
}
return value.toString();
}
private int lowIndex(final int lowest)
{
if (lowest < this.lowIndex) {
return lowest;
} else {
return this.lowIndex;
}
}
}
public static int bruteForceSearch(final int[] array, final int a,
final int b, final int c)
{
// search from c onward
/**
* search for the first value of the array that falls between a and b
* ignore everything before index of c
*/
for (int i = c; i < array.length; i++) {
if ((a < array[i]) && (array[i] < b)) {
return i;
}
}
return -1;
}
SegmentNode[] trees;
public ArraySearcher(final int[] array)
{
buildTree(array);
}
public void buildTree(final int[] array)
{
ArrayList<SegmentNode> mNodes = new ArrayList<SegmentNode>();
for (int i = 0; i < array.length; i++) {
SegmentNode mCurrentNode = new SegmentNode();
mCurrentNode.lowIndex = i;
mCurrentNode.min = array[i];
mCurrentNode.max = array[i];
mNodes.add(mCurrentNode);
}
ArrayList<SegmentNode> unsortedClone =
new ArrayList<SegmentNode>(mNodes);
// n (ln (n) )
Collections.sort(mNodes);
LinkedList<SegmentNode> nodesList = new LinkedList<SegmentNode>(mNodes);
this.trees = new SegmentNode[nodesList.size()];
for (int i = 0; i < this.trees.length; i++) {
this.trees[i] = merge(nodesList, 0, nodesList.size() - 1);
// we remove the ith one at each iteration
nodesList.remove(unsortedClone.get(i));
}
}
/**
*
* @param nodes
* @param i
* @param j
* @return
*/
public SegmentNode merge(final List<SegmentNode> nodes, final int i,
final int j)
{
if (i > j) {
throw new AssertionError();
}
SegmentNode left;
SegmentNode right;
int count = j - i;
if (count == 1) {
SegmentNode mParent = merge(nodes.get(i), nodes.get(i + 1));
return mParent;
} else if (count == 0) {
return nodes.get(i);
} else {
int mid = (count / 2) + i;
left = merge(nodes, i, mid);
right = merge(nodes, mid + 1, j);
}
return merge(left, right);
}
/**
* Build a parent segment from two other segments.
*
* @param a
* @param b
* @return
*/
public SegmentNode merge(final SegmentNode a, final SegmentNode b)
{
SegmentNode parent = new SegmentNode();
parent.root = parent;
parent.min = a.min;
parent.left = a;
parent.max = b.max;
parent.right = b;
if (a.lowIndex > b.lowIndex) {
parent.lowIndex = b.lowIndex;
b.root = parent;
} else {
parent.lowIndex = a.lowIndex;
a.root = parent;
}
return parent;
}
/**
* The work horse, find all the points with indexes greater than c that lie
* between a and b.
*
* @param a
* @param b
* @param c
* @return
*/
public Integer search(final int a, final int b, final int c)
{
if (c < this.trees.length) {
SegmentNode root = this.trees[c];
if ((a > root.max) || (b < root.min)) {
return null;
}
SegmentNode center = root.findCenter(a, b);
if (null == center) {
return null;
}
// special case to deal with a node with no children.
if ((center.left == null) && (center.right == null)) {
if ((a < center.min) && (b > center.max)) {
return new Integer(center.lowIndex);
} else {
return null;
}
}
Integer right = center.findRightmostLowestIndex(b);
Integer left = center.findLeftmostLowestIndex(a);
if ((null == right) && (null == left)) {
return null;
} else if (null == right) {
return left;
} else if (null == left) {
return right;
} else if (right.compareTo(left) > 0) {
return left;
} else {
return right;
}
} else {
return null;
}
}
}
by the way, I'm still using tests like this to verify it against the brute force approach:
static void testBoth(final int[] array, final ArraySearcher searcher,
final int a, final int b, final int c)
{
System.out.println("ArraySearcherTest.testBoth(array, mSearcher, " + a
+ ", " + b + ", " + c + ");");
int expected = ArraySearcher.bruteForceSearch(array, a, b, c);
Integer calcObj = searcher.search(a, b, c);
int calcInt = -1;
if (null != calcObj) {
calcInt = calcObj.intValue();
}
assertEquals(expected, calcInt);
}
@Test
public void randomizedProblemTester()
{
for (int i = 0; i < 100; i++) {
// build arrays from 5 to 20 elements long
int[] array = new int[TestUtils.randomInt(5, 20)];
System.out.print("int[] array = {");
for (int j = 0; j < array.length; j++) {
// with values from 0 to 100
array[j] = TestUtils.randomInt(0, 100);
System.out.print(array[j] + ", ");
}
System.out.println("};");
ArraySearcher mSearcher = new ArraySearcher(array);
for (int j = 0; j < array.length; j++) {
int a = TestUtils.randomInt(0, 100);
int b = TestUtils.randomInt(a, 100);
int c = TestUtils.randomInt(0, 20);
ArraySearcherTest.testBoth(array, mSearcher, a, b, c);
}
}
}