剑指 Offer 53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8 输出: 2 示例 2:

输入: nums = [5,7,7,8,8,10], target = 6 输出: 0

限制:

0 <= 数组长度 <= 50000

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解 :

  • 此题目给的数组为有序数组

  • 所以很容易想到二分搜索

  • 而统计一个元素,在有序数组中出现的次数,也就是求此元素左右的下标

  • 分别查找 左,右索引,使用二分搜索

    • 在查找 右边下标

      • 当 中间值 nums[mid] <= target时, 往右边找
        • 这里需要注意的是,=等情况,因为我们是要查找右边下标,所以依然需要往右边找
      • 否则,往左边查找
    • 在查找 左边下标

      • 当中间值 nums[mid] >= target时, 往左边找
        • 依然需要注意, =的情况,因为要查找最左边下标,所以需要继续往左边查找
  • 右下标 - 左下标 + 1 也就是 元素出现的次数

代码如下 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return 0;

        // 二分法找 值为target 的最右边索引
        int right = 0;
        int begain = 0, end = nums.length - 1;
        while (begain <= end){
            int mid = (begain + end) >> 1;
            if (nums[mid] <= target){
                // 往右边找
                begain = mid + 1;
            }else {
                // 往左边找
                end = mid - 1;
            }
        }
        right = begain;
        // 如果没找到
        if(end >= 0 && nums[end] != target) return 0;

        // 二分法找 值为targe 的最左边索引
        int left = 0;
        begain = 0;
        end = nums.length - 1;
        while (begain <= end){
            int mid = (begain + end) >> 1;
            if (nums[mid] >= target){
                // 往左边找
                end = mid - 1;
            }else {
                // 往右边找
                begain = mid + 1;
            }
        }
        left = end;

        return right - left - 1;
    }

优化

  • 边代码过于臃肿,有两遍 二分搜索 的代码 几乎一样
  • 所以下边,我们尝试优化代码,将 二分搜索的代码提取公用方法
  • 提取binarySearch方法,查找 target 在 nums数组中的右边界的下一个数字
  • 因为此题目中,数组中数字都是int整数
  • 所以,binarySearch 查找 target 的 右边界 - target-1的右边界
  • 即可得到最终结果
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public int search1(int[] nums, int target) {
    
    // 因为此题目中,数组都为int整数
    // 所以,binarySearch查找 target 右边界,再查找 target - 1的右边界
    // 相减 即可 得到 结果
    return binarySearch(nums, target) - binarySearch(nums, target - 1);
}

// 二分查找 右边界
// 其实就是查找 target 右边 第一个不为 target 的下标
int binarySearch(int[] nums, int target) {
    int i = 0, j = nums.length - 1;
    while(i <= j) {
        int m = (i + j) / 2;
        if(nums[m] <= target) 
            i = m + 1;
        else 
            j = m - 1;
    }
    return i;
}

截屏2020-06-25下午11.10.03

复杂度分析 :

时间复杂度 : O(log N)

空间复杂度 : O(1)

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8 输出: [3,4] 示例 2:

输入: nums = [5,7,7,8,8,10], target = 6 输出: [-1,-1]

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解 :

  • 此题目与第一个道题,基本一致
  • 不同之处仅在于结果的最终输出形式
  • 这里不再赘述了,直接贴代码

代码如下 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static int[] searchRange(int[] nums, int target) {

    if (nums == null || nums.length == 0) return new int[]{-1,-1};

    // 二分法找 值为target 的最右边索引
    int right = 0;
    int begain = 0, end = nums.length - 1;
    while (begain <= end){
        int mid = (begain + end) >> 1;
        if (nums[mid] <= target){
            // 往右边找
            begain = mid + 1;
        }else {
            // 往左边找
            end = mid - 1;
        }
    }
    right = begain;
    // 如果没找到
    if (end < 0 || nums[end] != target) return new int[]{-1,-1};

    // 二分法找 值为targe 的最左边索引
    int left = 0;
    begain = 0;
    end = nums.length - 1;
    while (begain <= end){
        int mid = (begain + end) >> 1;
        if (nums[mid] >= target){
            // 往左边找
            end = mid - 1;
        }else {
            // 往右边找
            begain = mid + 1;
        }
    }
    left = end;

    int[] res = new int[2];
    res[0] = left + 1;
    res[1] = right - 1;
    return res;
}

优化

  • 同上述题目一样
  • 我们也做一下臃肿代码的优化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 public static int[] searchRange(int[] nums, int target) {
        if (nums == null || nums.length == 0) return new int[]{-1,-1};

        int right = binarySearch(nums, target);
        int left = binarySearch(nums, target - 1);

   		  // 判断left有效性
   			// 不在范围, 或者其值 != target。 则说明没找到元素
        if (left < 0 || left >= nums.length || nums[left] != target) return new int[]{-1,-1};
   
        return new int[]{left, right - 1};
    }

    // 找右边界
    public static int binarySearch(int[] nums, int target){
        int begain = 0, end = nums.length - 1;
        while (begain <= end){
            int mid = (begain + end) >> 1;
            if (nums[mid] <= target){
                begain = mid + 1;
            }else {
                end = mid - 1;
            }
        }
        return begain;
    }

复杂度分析 :

时间复杂度 : O(log N)

空间复杂度 : O(1)

截屏2020-06-25下午11.31.28

总结

  • 通过这两道题,至少学会了一个知识点
  • 之前只用二分来查找排序数组中,是否存在某元素
  • 现在学会了,通过二分查找,查找元素在数组中的起始位置 和 结束位置
  • 其实重点,就在 = 时逻辑的处理
    • 在查找右边界<= 都往右查找
    • 在查找左边界>= 都往左查找