二分查找-LeftRightmost

168 阅读4分钟

传统的二分查找如下:

public static void main(String[] args)
    {
        int arr[] = {1, 2, 4, 4, 4, 5, 6};
        int refen = Refen(arr, 4);
        System.out.println(refen);
    }

    private static int Refen(int arr[], int value)
    {
        int i = 0;
        int j = arr.length;
        int mid = 0;
        while (i < j)
        {
            mid = (i + j) >>> 1;
            if (arr[mid] < value)
            {
                i = mid + 1;
            }
            else if (arr[mid] > value)
            {
                j = mid - 1;
            }
            else
            {
                return mid;
            }
        }
        return -1;
    }

打印结果:

3

问题引出,数组中4的最左边出现索引值是2 但是mid刚好计算时是3而3的值又是4所以就直接拿出来了,我们想要的是数组中最左边的目标值 该怎么做呢?

优化代码:

public static void main(String[] args)
    {
        int arr[] = {1, 2, 4, 4, 4, 5, 6};
        int refen = Refen(arr, 4);
        System.out.println(refen);
    }

    private static int Refen(int arr[], int value)
    {
        int i = 0;
        int j = arr.length;
        int mid = 0;
        // 候选者
        int candidate = -1;
        while (i <= j)
        {
            mid = (i + j) >>> 1;
            if (value < arr[mid])
            {
                j = mid - 1;
            }
            else if (arr[mid] < value)
            {
                i = mid + 1;
            }
            else
            {
                // 记录候选者
                candidate = mid;
                // 记录候选位置后 将j向后移动
                j = mid - 1;
            }
        }
        return candidate;
    }

打印结果:

2

mid = (0 + 7) / 2 = 3

arr[mid] = 4 < value = 4 不满足 走 else 记录了 候选者 然后 j = mid - 1;向左移动

然后计算中间值 mid = (0 + 3) / 2 = 1;

arr[mid]=2 < value=4满足条件,走i = mid + 1;

计算中间值,mid = (2 + 2) / 2 = 2

此时 if 判断都不满足 走 else 记录了 候选者 然后 j = mid - 1; 下一轮就没有循环机会了 然后返回 当前的候选者。 也就是 2

那有左边查找也就有右边查找 我们只需要更改如下代码就行了:

public static void main(String[] args)
    {
        int arr[] = {1, 2, 4, 4, 4, 5, 6};
        int refen = Refen(arr, 4);
        System.out.println(refen);
    }

    private static int Refen(int arr[], int value)
    {
        int i = 0;
        int j = arr.length;
        int mid = 0;
        // 候选者
        int candidate = -1;
        while (i <= j)
        {
            mid = (i + j) >>> 1;
            if (value < arr[mid])
            {
                j = mid - 1;
            }
            else if (arr[mid] < value)
            {
                i = mid + 1;
            }
            else
            {
                // 记录候选者
                candidate = mid;
                // 记录候选位置后 将j向后移动
                i = mid + 1;
            }
        }
        return candidate;
    }

打印结果:

4

Leftmost二分查找算法,与RightMost二分查找算法它们都是找不到的时候返回 -1。往后学就会发现它返回的 -1 其实没法用,我们可以让 -1 返回 一个更有用的值

优化后代码如下:

public static void main(String[] args)
    {
        int arr[] = {1, 2, 4, 4, 4, 5, 6};
        int refen = dan(arr, 3);
        System.out.println(refen);
    }
    private static int dan(int arr[], int va)
    {
        int i = 0;
        int j = arr.length;
        int mid = 0;
        while(1 < j - i)
        {
            mid = (i + j) >>> 1;
            // 当中间值小于目标值就向右找
            if(arr[mid] < va)
            {
                i = mid + 1;
            // 当中间值大于目标值就向左找
            }else
            {
                j = mid - 1;
            }
        }
        return i - 1;
    }

打印结果:

2

比如需要查找的目标值为 3

计算出中间值

arr[mid] = 4 > va = 3,所以 mid = j - 1;

计算中间值

arr[mid] = 1 < va = 3,所以 i = mid + 1;

j 与 i 相同 循环结束,返回了 i 索引 就是 对应的元素值为 2,返回了小于3的右边的值

与之对应的左边值查找代码如下:Leftrightmost

public static void main(String[] args)
    {
        int arr[] = {1, 2, 4, 4, 4, 5, 6};
        int refen = dan(arr, 3);
        System.out.println(refen);
    }
    private static int dan(int arr[], int va)
    {
        int i = 0;
        int j = arr.length;
        int mid = 0;
        while(1 < j - i)
        {
            mid = (i + j) >>> 1;
            // 当中间值小于目标值就向右找
            if(arr[mid] < va)
            {
                i = mid + 1;
            // 当中间值大于目标值就向左找
            }else
            {
                j = mid - 1;
            }
        }
        return i;
    }

打印结果:

2

应用场景

前任:就是比target小

后任:就是比target大

最近邻居:就是在前任和后任 加在一起看谁离得更近,比如4 跟 5中间距离了一个位置,而7跟5中间距离了2个位置,所以4就是5的最近邻居

相似的leetcode题目

题目要求

给定一个排序的整数数组 nums 和一个整数目标值 target ,请在数组中找到 target ,并返回其下标。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0

示例 5:

输入: nums = [1], target = 0
输出: 0

提示:

  • 1 <= nums.length <= 104
  • 104 <= nums[i] <= 104
  • nums 为无重复元素升序排列数组
  • 104 <= target <= 104
int i = 0, j = nums.length - 1;
        while(i <= j)
        {
            int mid = (i + j) >>> 1;
            if(target <= nums[mid])
            {
                j = mid - 1;
            }else
            {
                i = mid + 1;
            }
        }
        return i;