0

In a sorted array, you can use binary search to perform many different kinds of operations:

  1. to find the first element smaller than the target
  2. to find the first element bigger than the target or,
  3. to find the first element smaller or equal than the target
  4. to find the first element bigger or equal than the target or even specific
  5. to find the element closest to the target.

You can find answers to #2 and #5 in stack overflow, the answer of which are using mutations of binary search, however there is no fixed algorithm to answer those questions, specifically in adjusting the indices.

For example, in question 3, find the first smaller or equal element in sorted array than target: Given int[] stocks = new int[]{1, 4, 3, 1, 4, 6};, I want to find the first element smaller than 5. It should return 4 after sorting and my code goes like:

private static int findMin(int[] arr, int target) {
    Arrays.sort(arr);
    int lo = 0;
    int hi = arr.length - 1;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (arr[mid] == target) return mid;
        if (arr[mid] > target) {
            hi = mid - 1;
        } else {
            lo = mid;
        }
    }
    return lo;
}

The logic here is:

  1. if you find mid element equals to target, you just return the mid
  2. if you find mid element bigger than the target, you just discard the mid and everything bigger than it
  3. if you find mid element is smaller than the target, the mid element could be an answer, you want to keep it, but discard anything smaller than it.

If you run it, it actually goes into a infinite loop, but just tweak the start index of hi = arr.length - 1 to hi = arr.length;, it actually works well. I have no clue how to really set all the conditions up: how to write conditions, what to set start index of hi and lo and use lo<=hi or lo < hi.

Any help?

2
  • If you can use Arrays.sort(arr), why not use Arrays.binarySearch(arr, target)? Commented Jun 5, 2018 at 5:21
  • @Andreas, I don't quite get it, since I am not really searching for that target. Commented Jun 7, 2018 at 3:30

4 Answers 4

1

Basically in the above mentioned case ,you need the greatest element which is smaller than the given value, i.e you need to find floor of the given element. This can be easily done using binary search in O(logn), time :

The cases which you need to consider are as follows:

  1. If last element is smaller than x, than return the last element.

  2. If middle point is floor, than return mid.

  3. If element is not found in both of the above cases, than check if the element lies between mid and mid -1. If it is so than return mid-1.
  4. Else keep iterating on left half or right half until you find a element that satisfies your condition. Select right half or left half based on the check that given value is greater than mid or less than mid.

Try the following:

static int floorInArray(int arr[], int low, int high, int x)
{
    if (low > high)
        return -1;

    // If last element is smaller than x
    if (x >= arr[high])
        return high;

    // Find the middle point
    int mid = (low+high)/2;

    // If middle point is floor.
    if (arr[mid] == x)
        return mid;

    // If x lies between mid-1 and mid
    if (mid > 0 && arr[mid-1] <= x && x < arr[mid])
        return mid-1;

    // If x is smaller than mid, floor
    // must be in left half.
    if (x < arr[mid])
        return floorInArray(arr, low, mid - 1, x);

    // If mid-1 is not floor and x is
    // greater than arr[mid],
    return floorInArray(arr, mid + 1, high,x);
}
Sign up to request clarification or add additional context in comments.

7 Comments

This is actually great when you do check arr[mid - 1] <= x && x < arr[mid]). Can you apply this to other questions?
It will solve your problem. According to my understanding you need the greatest element which is smaller than the given value?
Yes, the greatest element equal or smaller than the given value. Your answer pretty much solved it, but I haven't run many test cases yet. And I am doing it in a loop way.
@zhen you can easily convert this solution to loop.
Already done, I am applying your answer to other questions I mentioned above. Nice edge case handling.
|
0

In your while loop, you don't have a case set for when hi == lo

This case is applicable when you are iterating the last element or array has only 1 element.

Set the while loop as while(lo <= hi) and it will terminate when all elements are searched

Or set an if case inside loop for when hi is equal to lo.

if(hi == lo)

2 Comments

That's a good idea to check when hi == lo, but in this problem, if I don't change hi = arr.length - 1, even if you check that, still infinite if you would like to try.
thats most probably due to < operator checking only till 1 before the element, i.e. If you set while(x < arr.length) it will only hold true, if x is less than or equal to (arr.length - 1).
0

Rather than implementing your own binary search, you can just use Arrays.binarySearch(int[] a, int key), then adjust the returned value accordingly.

Returns index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). The insertion point is defined as the point at which the key would be inserted into the array: the index of the first element greater than the key, or a.length if all elements in the array are less than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the key is found.

Your rules didn't specify which index to return when there are multiple valid choices (#3 or #4 with multiple equal values, or #5 with equidistant values), so the code below has code making an explicit choice. You can remove the extra code, if you don't care about ambiguities, or change the logic if you disagree with my decision to resolve it.

Note that when return value is <0, returnValue = -insertionPoint - 1, which means that insertionPoint = -returnValue - 1, which in code below mean -idx - 1. Index prior to insertion point is therefore -idx - 2.

The methods may of course return out-of-range index values (-1 or arr.length), so caller always need to check for that. For closest() method, that can only happen if array is empty, in which case it returns -1.

public static int smaller(int[] arr, int target) {
    int idx = Arrays.binarySearch(arr, target);
    if (idx < 0) {
        // target not found, so return index prior to insertion point
        return -idx - 2;
    }
    // target found, so skip to before target value(s)
    do {
        idx--;
    } while (idx >= 0 && arr[idx] == target);
    return idx;
}

public static int smallerOrEqual(int[] arr, int target) {
    int idx = Arrays.binarySearch(arr, target);
    if (idx < 0) {
        // target not found, so return index prior to insertion point
        return -idx - 2;
    }
    // target found, so skip to last of target value(s)
    while (idx < arr.length - 1 && arr[idx + 1] == target) {
        idx++;
    }
    return idx;
}

public static int biggerOrEqual(int[] arr, int target) {
    int idx = Arrays.binarySearch(arr, target);
    if (idx < 0) {
         // target not found, so return index of insertion point
        return -idx - 1;
    }
    // target found, so skip to first of target value(s)
    while (idx > 0 && arr[idx - 1] == target) {
        idx--;
    }
    return idx;
}

public static int bigger(int[] arr, int target) {
    int idx = Arrays.binarySearch(arr, target);
    if (idx < 0) {
         // target not found, so return index of insertion point
        return -idx - 1;
    }
    // target found, so skip to after target value(s)
    do {
        idx++;
    } while (idx < arr.length && arr[idx] == target);
    return idx;
}

public static int closest(int[] arr, int target) {
    int idx = Arrays.binarySearch(arr, target);
    if (idx >= 0) {
        // target found, so skip to first of target value(s)
        while (idx > 0 && arr[idx - 1] == target) {
            idx--;
        }
        return idx;
    }
    // target not found, so compare adjacent values
    idx = -idx - 1; // insertion point
    if (idx == arr.length) // insert after last value
        return arr.length - 1; // last value is closest
    if (idx == 0) // insert before first value
        return 0; // first value is closest
    if (target - arr[idx - 1] > arr[idx] - target)
        return idx; // higher value is closer
    return idx - 1; // lower value is closer, or equal distance
}

Test

public static void main(String... args) {
    int[] arr = {1, 4, 3, 1, 4, 6};
    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr));

    System.out.println("  |         Index        |        Value        |");
    System.out.println("  |   <  <=   ~  >=   >  |  <  <=   ~  >=   >  |");
    System.out.println("--+----------------------+---------------------+");
    for (int i = 0; i <= 7; i++)
        test(arr, i);
}

public static void test(int[] arr, int target) {
    int smaller        = smaller       (arr, target);
    int smallerOrEqual = smallerOrEqual(arr, target);
    int closest        = closest       (arr, target);
    int biggerOrEqual  = biggerOrEqual (arr, target);
    int bigger         = bigger        (arr, target);
    System.out.printf("%d | %3d %3d %3d %3d %3d  |%3s %3s %3s %3s %3s  | %d%n", target,
                      smaller, smallerOrEqual, closest, biggerOrEqual, bigger,
                      (smaller < 0 ? "" : String.valueOf(arr[smaller])),
                      (smallerOrEqual < 0 ? "" : String.valueOf(arr[smallerOrEqual])),
                      (closest < 0 ? "" : String.valueOf(arr[closest])),
                      (biggerOrEqual == arr.length ? "" : String.valueOf(arr[biggerOrEqual])),
                      (bigger == arr.length ? "" : String.valueOf(arr[bigger])),
                      target);
}

Output

[1, 1, 3, 4, 4, 6]
  |         Index        |        Value        |
  |   <  <=   ~  >=   >  |  <  <=   ~  >=   >  |
--+----------------------+---------------------+
0 |  -1  -1   0   0   0  |          1   1   1  | 0
1 |  -1   1   0   0   2  |      1   1   1   3  | 1
2 |   1   1   1   2   2  |  1   1   1   3   3  | 2
3 |   1   2   2   2   3  |  1   3   3   3   4  | 3
4 |   2   4   3   3   5  |  3   4   4   4   6  | 4
5 |   4   4   4   5   5  |  4   4   4   6   6  | 5
6 |   4   5   5   5   6  |  4   6   6   6      | 6
7 |   5   5   5   6   6  |  6   6   6          | 7

1 Comment

I agreed that your method would work, but using a while loop to solve it iteratively lose the O(logn).
0

Try Treesets. If your input is array, follow below steps:

  1. convert array to hash set . src:https://www.geeksforgeeks.org/program-to-convert-array-to-set-in-java/ 2.convert hash set to Tree set. Tree sets will store the values in sorted order with no duplicates.
  2. Now, call Tree set methods like higher(),ceiling(),floor(),lower() methods as per your requirement.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.