找到排序数组中小于目标的第一个元素

时间:2018-06-05 04:23:33

标签: java arrays binary-search

在排序数组中,您可以使用二进制搜索来执行许多不同类型的操作:

  1. 找到小于目标的第一个元素
  2. 找到比目标大的第一个元素 或者,
  3. 找到小于或等于目标的第一个元素
  4. 找到大于或等于目标的第一个元素 甚至具体的
  5. 找到最接近目标的元素。
  6. 你可以在堆栈溢出中找到#2和#5的答案,答案是使用二进制搜索的突变,但是没有固定的算法来回答这些问题,特别是在调整索引时。

    例如,在问题3中,找到排序数组中第一个小于或等于元素的元素: 给定int[] stocks = new int[]{1, 4, 3, 1, 4, 6};,我想找到小于5的第一个元素。它应该在排序后返回4,我的代码如下:

    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;
    }
    

    这里的逻辑是:

    1. 如果您发现mid元素等于target,则只返回mid
    2. 如果你发现mid元素大于目标,你只需丢弃mid和比它大的一切
    3. 如果你发现mid元素小于目标,mid元素可能是一个答案,你想保留它,但丢弃任何小于它的东西。
    4. 如果你运行它,它实际上进入了一个无限循环,但只是将hi = arr.length - 1的起始索引调整为hi = arr.length;,它实际上运行良好。我不知道如何真正设置所有条件:如何编写条件,如何设置hi和lo的起始索引并使用lo<=hilo < hi

      任何帮助?

4 个答案:

答案 0 :(得分:0)

基本上在上面提到的情况下,你需要一个小于给定值的最大元素,即你需要找到给定元素的底面。 这可以使用O(logn)中的二进制搜索,时间:

轻松完成

您需要考虑的案例如下:

  1. 如果最后一个元素小于x,则返回最后一个元素。

  2. 如果中间点是楼层,则返回中间位置。

  3. 如果在上述两种情况下都找不到元素,则检查元素是否位于中间和中间-1之间。如果是这样,那么返回中间1。
  4. 继续迭代左半边或右半边,直到找到满足条件的元素。根据给定值大于mid或小于mid的检查,选择右半部分或左半部分。
  5. 尝试以下方法:

    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);
    }
    

答案 1 :(得分:0)

在您的while循环中,您没有为hi == lo

设置案例集

当您迭代最后一个元素或数组只有一个元素时,这种情况适用。

将while循环设置为while(lo <= hi),并在搜索所有元素时终止

或者在hi等于lo时设置if case内循环。

if(hi == lo)

答案 2 :(得分:0)

您可以只使用Arrays.binarySearch(int[] a, int key),而不是实现自己的二分查找,然后相应地调整返回值。

  

返回搜索键的索引(如果它包含在数组中);否则,( - (插入点) - 1)。 插入点定义为将键插入数组的点:第一个元素的索引大于键,如果数组中的所有元素都小于,则为a.length指定的密钥。请注意,当且仅当找到密钥时,这可以保证返回值>> =。

当有多个有效选择时,您的规则没有指定要返回的索引(#3或#4具有多个相等的值,或者#5具有等距值),因此下面的代码具有明确选择的代码。如果您不关心歧义,可以删除额外的代码,如果您不同意我的决定,则可以更改逻辑。

请注意,当返回值为&lt; 0时,returnValue = -insertionPoint - 1,这意味着insertionPoint = -returnValue - 1,其中代码低于-idx - 1。因此,插入点之前的索引-idx - 2

这些方法当然可以返回超出范围的索引值(-1arr.length),因此调用者总是需要检查它。对于closest()方法,只有在数组为空时才会发生这种情况,在这种情况下它返回-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
}

测试

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);
}

输出

[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

答案 3 :(得分:0)

尝试树集。如果您输入的是数组,请执行以下步骤:

  1. 将数组转换为哈希集。 src:https://www.geeksforgeeks.org/program-to-convert-array-to-set-in-java/ 2.将哈希集转换为树集。 树集将按排序顺序存储值,没有重复项。
  2. 现在,根据您的需要调用树集方法,例如high(),ceiling(),floor(),lower()方法。