刷题笔记 Hot100 34. 在排序数组中查找元素的第一个和最后一个位置

根据题意,显然需要使用二分查找解决问题,下面给出 递增数组 的二分查找模板
注:使用二分查找时,数组必须有序
public int binarySearch(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    while (left <= right) {
        // mid用这种定义方式,数据不会溢出
        int mid = left + (right - left) / 2;
        if (target > nums[mid]) {
            // 数组递增,target比当前值大,到右半边寻找
            left = mid + 1;
        } else if (target < nums[mid]) {
            // 数组递增,target比当前值小,到左半边寻找
            right = mid - 1;
        } else {
            // 找到数据的下标并返回
            return mid;
        }
    }
    // 未找到,返回-1
    return -1;
}
将二分查找引入,先找到数组中等于target的元素的下标,然后使用双指针,一个指针向左,一个指针向右,分别找到最左边的target和最右边的target,将这两个下边返回即可。
如果没有找到,直接返回{-1, -1}
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int length = nums.length, i = 0, j = length - 1, index = 0;
        boolean flag = false;
        // 时间复杂度为O(logN),结合数组升序(有序),显然需要使用二分查找,找到target的下标
        while (i <= j) {
            int mid = (i + j) / 2;
            if (target > nums[mid]) {
                i = mid + 1;
            } else if (target < nums[mid]) {
                j = mid - 1;
            } else {
                flag = true;
                // 记录target的下标
                index = mid;
                break;
            }
        }
        if (!flag) return new int[]{-1, -1};
        // 从target的下标开始向左与向右搜索,直到值不等于target
        i = j = index;
        while (i >= 0 && nums[i] == target) i--;
        while (j < length && nums[j] == target) j++;
        return new int[]{i + 1, j - 1};
    }
}
然而,根据上面的处理,我们会发现时间复杂度并不是简单的O(logN),因为还引入了左右两个指针。假设一种最极端的情况,整个数组中所有的元素值都为target,那么左右指针还是会完整遍历整个数组,时间复杂度依然是O(N)。因此,需要想一个办法,继续使用二分查找来寻找符合条件的target

那么如何才能找到target的时候不返回,而是继续向左或者向右寻找符合条件的target呢?这里可以分别使用两个二分查找: 一个不断向左寻找,找到最左边的target; 一个负责向右寻找,找到最右边的target
这里又引申出了新的问题,怎么样才能找到最左边或最右边的target? 

解决办法其实很简单,以寻找最左边的target为例,当 nums[mid] == target 时,先不要急着返回,看看nums[mid - 1]是不是和nums[mid]相等(同时要求mid > 0,如果 mid == 0,说明当前下标是数组第一个元素,自然不用继续往前寻找),如果相等,说明对于当前的mid位置,并不是最左边的target,也就是说,target需要向左寻找,也就是说,寻找的区间要放在左边,相当于 right = mid - 1

同理,向右寻找时,如果找到了 nums[mid] == target 时,不要急着返回,看看nums[mid + 1]和nums[mid]是否相等(同时要求mid < length - 1,若mid == length - 1,说明当前mid指向的元素是数组中最后一个元素,也就是最右边的target),如果相等,就要继续向右寻找,寻找区间要移到右边,等价于left = mid + 1

综上,我们对上面的代码进行修改,使用两个binarySearch找到左右边界

public int[] searchRange(int[] nums, int target) {
        int left = leftBinarySearch(nums, target);
        int right = rightBinarySearch(nums, target);
        return new int[]{left, right};
    }

    private int leftBinarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            } else {
                // 如果mid是数组的第一个元素或nums[mid - 1] != nums[mid],说明当前mid指向的就是最左边的target
                if (mid == 0 || nums[mid - 1] != nums[mid]) return mid;
                // 左边还有target,继续向左区间寻找
                right = mid - 1;
            }
        }
        return -1;
    }

    private int rightBinarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target > nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            } else {
                // 如果mid是数组最右边的元素或nums[mid + 1] != nums[mid],说明当前mid指向的就是最右边的target
                if (mid == nums.length - 1 || nums[mid + 1] != nums[mid]) return mid;
                // 右边还有target,继续向右区间寻找
                left = mid + 1;
            } 
        }
        return -1;
    }
本题得解

你可能感兴趣的:(算法,leetcode,数据结构)