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

大家好,我是吴师兄。

今天继续来学习《剑指Offer》系列的一道经典题目,依旧给出了非常详细的题解和精美的配图与动画。

# 一、题目描述

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

示例 1:

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

示例 2:

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

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

# 二、题目解析

为了帮助你更好的理解整个过程,我特意做了一组动画,点开可以查看

# 三、参考代码

// 登录 AlgoMooc 官网获取更多算法图解
// https://www.algomooc.com
// 作者:程序员吴师兄
class Solution {
    public int search(int[] nums, int target) {
        
        // 寻找目标值在数组中的开始位置
        int firstIdx = findBeginPostion(nums,target);

        // 寻找目标值在数组中的结束位置
        int lastIdx = findEndPostion(nums,target);

        // 1、如果在数组中找不到目标值的开始位置,同时也找不到目标值的结束位置
        // 那说明目标值不存在,返回 0
        if( firstIdx == -1 && lastIdx == -1 ) return 0;

        // 2、如果在数组中只能找到目标值的开始位置和结束位置其中一个
        // 那说明目标值的个数只有一个,返回 1
        if( firstIdx == -1 || lastIdx == -1 ) return 1;

        // 3、如果在数组中可以找到目标值的开始位置,同时也可以找到目标值的结束位置
        // 那说明目标值的个数有多个,返回 lastIdx - firstIdx + 1;
        return lastIdx - firstIdx + 1;

    }

    // 寻找目标值在数组中的开始位置
    private int findBeginPostion(int[] nums , int target){

        // left 指向当前区间的最左边位置,所以初始化为 0
        int left = 0;

        // right 指向当前区间的最右边位置,所以初始化为 nums.length - 1
        int right = nums.length - 1;

        // 循环进行二分查找,直到左端点位置超过了右端点
        // 或者在循环过程中找到了起始位置
        while(left <= right){

            // 计算当前区间的中间位置,向下取整
            int mid = (left + right) / 2;

            // 如果中间位置的元素值等于目标值 target
            if(nums[mid] == target){

                // 并且中间位置 mid 的左边没有元素,即中间位置 mid 为当前区间的起始位置
                // 或者中间位置 mid 的前一个元素小于目标值 target
                // 说明 mid 指向了 target 的起始位置
                if(mid == 0 || nums[mid - 1] < target){
                    // mid 指向了 target 的起始位置,返回这个结果
                    return mid;
                }

                // 否则,说明 mid 的左边依然有元素值等于 target
                // 那么 mid 就不是 target 的起始位置,需要在 mid 的左边进行查找
                // 所以缩小范围为 left 到 mid - 1
                // 当前区间的左侧为 left,右侧 right = mid - 1
                right = mid - 1;

            // 如果中间位置的元素值大于目标值 target
            // 说明需要在 mid 的左边进行查找
            }else if( nums[mid] > target ){

                // 所以缩小范围为 left 到 mid - 1
                // 当前区间的左侧为 left,右侧 right = mid - 1
                right = mid  - 1;

            // 如果中间位置的元素值小于目标值 target
            // 说明需要在 mid 的右边进行查找
            }else{

                // 所以缩小范围为 mid + 1 到 right
                // 当前区间的左侧为 left = mid + 1,右侧 right 
                left = mid + 1;

            }
        }
        
        // 如果循环结束后还没有返回,说明找不到目标值 target,返回 -1
        return  -1;
    }

    // 寻找目标值在数组中的结束位置
    private int findEndPostion(int[] nums , int target){

        // left 指向当前区间的最左边位置,所以初始化为 0
        int left = 0;

        // right 指向当前区间的最右边位置,所以初始化为 nums.length - 1
        int right = nums.length - 1;

        // 循环进行二分查找,直到左端点位置超过了右端点
        // 或者在循环过程中找到了结束位置    
        while(left <= right){

            // 计算当前区间的中间位置,向下取整
            int mid = (left + right)  / 2;

            // 如果中间位置的元素值等于目标值 target
            if(nums[mid]  == target){
                // 并且中间位置 mid 的右边没有元素,即中间位置 mid 为当前区间的结束位置
                // 或者中间位置 mid 的后一个元素大于目标值 target
                // 说明 mid 指向了 target 的结束位置
                if(mid == nums.length - 1 || nums[mid + 1] > target){
                    // mid 指向了 target 的结束位置,返回这个结果
                    return mid;
                }

                // 否则,说明 mid 的右边依然有元素值等于 target
                // 那么 mid 就不是 target 的结束位置,需要在 mid 的右边进行查找
                // 所以缩小范围为 mid + 1 到 right
                // 当前区间的左侧为 left = mid + 1 ,右侧为 right 
                left =  mid + 1;

            // 如果中间位置的元素值大于目标值 target
            // 说明需要在 mid 的左边进行查找
            }else if(nums[mid] > target){

                // 所以缩小范围为 left 到 mid - 1
                // 当前区间的左侧为 left,右侧 right = mid - 1
                right  = mid  - 1;

            // 如果中间位置的元素值小于目标值 target
            // 说明需要在 mid 的右边进行查找
            }else{

                // 所以缩小范围为 mid + 1 到 right
                // 当前区间的左侧为 left = mid + 1,右侧 right 
                left = mid + 1;

            }
        }

        // 如果循环结束后还没有返回,说明找不到目标值 target,返回 -1
        return  - 1;
    }

}