【问题标题】:Find the first element in a sorted array that is smaller than the target查找排序数组中小于目标的第一个元素
【发布时间】:2018-11-14 11:07:44
【问题描述】:

在已排序的数组中,您可以使用二分查找来执行许多不同类型的操作:

  1. 找到小于目标的第一个元素
  2. 找到比目标大的第一个元素 或者,
  3. 查找小于或等于目标的第一个元素
  4. 查找大于或等于目标的第一个元素 甚至是具体的
  5. 找到最接近目标的元素。

您可以在堆栈溢出中找到#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. 如果你发现中间元素等于目标,你只需返回中间元素
  2. 如果您发现中间元素比目标大,您只需丢弃中间元素和比它大的所有元素
  3. 如果您发现中间元素小于目标,中间元素可能是一个答案,您希望保留它,但丢弃任何小于它的元素。

如果你运行它,它实际上会进入一个无限循环,但只需将hi = arr.length - 1 的起始索引调整为hi = arr.length;,它实际上运行良好。我不知道如何真正设置所有条件:如何编写条件,设置 hi 和 lo 的起始索引以及使用 lo&lt;=hilo &lt; hi

有什么帮助吗?

【问题讨论】:

标签: java arrays binary-search


【解决方案1】:

基本上在上述情况下,您需要小于给定值的最大元素,即您需要找到给定元素的下限。 这可以使用 O(logn), time 中的二进制搜索轻松完成:

您需要考虑的情况如下:

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

  2. 如果中间点是地板,则返回中间。

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

尝试以下方法:

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

【讨论】:

  • 当您检查arr[mid - 1] &lt;= x &amp;&amp; x &lt; arr[mid]) 时,这实际上很棒。您可以将此应用于其他问题吗?
  • 它将解决您的问题。根据我的理解,您需要小于给定值的最大元素?
  • 是的,最大元素等于或小于给定值。您的回答几乎解决了它,但我还没有运行很多测试用例。而且我是以循环方式进行的。
  • @zhen 您可以轻松将此解决方案转换为循环。
  • 已经完成,我将您的答案应用于我上面提到的其他问题。很好的边缘案例处理。
【解决方案2】:

在您的 while 循环中,您没有为 when hi == lo 设置大小写

这种情况适用于迭代最后一个元素或数组只有 1 个元素的情况。

将while循环设置为while(lo &lt;= hi),当所有元素都被搜索到时它将终止

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

if(hi == lo)

【讨论】:

  • 检查 hi == lo 的时间是个好主意,但是在这个问题中,如果我不更改 hi = arr.length - 1,即使你检查了,如果你愿意,仍然是无限的试试看。
  • 这很可能是由于
【解决方案3】:

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

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

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

注意,当返回值为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

【讨论】:

  • 我同意你的方法可行,但使用 while 循环迭代地解决它会丢失 O(logn)。
【解决方案4】:

尝试树集。如果您的输入是数组,请按照以下步骤操作:

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

【讨论】:

    猜你喜欢
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 2020-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-06
    相关资源
    最近更新 更多