剑指 Offer在排序数组中查找数字 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
大部分思想转自:
作者:jyd
链接:https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/solution/mian-shi-ti-53-i-zai-pai-xu-shu-zu-zhong-cha-zha-5/

我的思路

class Solution {
    public int search(int[] nums, int target) {
        int i = 0;
        int j = nums.length-1;
        int count = 0;
        while(i<=j){
            int mid = i + (j-i)/2;
            //找到当前节点之后,从当前节点向右查找与target值相同的节点。
            //从当前节点向左查找与target值相同的节点。
            //返回target节点数量。
            if(nums[mid]==target){
                 for(int k = mid + 1;k<nums.length;k++){
                     if(nums[k]==target){
                         count++;
                     }
                 }
                 for(int k =mid-1;k>=0;k--){
                     if(nums[k]==target){
                         count++;
                     }
                 }
                 return count + 1;
            }else if(nums[mid]<target){
                i = mid + 1;
            }else{
                j = mid -1;
            }
        }
        return count;
    }
}

我的想法和顺序查找一样,时间复杂度还是o(N),怎么充分利用二分查找的优点呢?

二分查找查找到的位置一定和需要查找的值相当接近。如下图所示,如果查找target=6,那么最终返回值left = 1,right = 5;

在这里插入图片描述

排序数组 nums中的所有数字 target 形成一个窗口,记窗口的 左 / 右边界 索引分别为 left 和 right ,分别对应窗口左边 / 右边的首个元素。
本题要求统计数字 target 的出现次数,可转化为:使用二分法分别找到 左边界 left 和 右边界 right ,易得数字 target 的数量为 right − left − 1 。
初始化: 左边界 i = 0,右边界 j=nums.length−1 。
循环二分: 当闭区间 [i, j] 无元素时跳出;
计算中点 m = (i + j) / 2(向下取整);
	若 nums[m] < target,则 target 在闭区间 [m + 1, j]中,因此执行 i = m + 1;
	若 nums[m] > target,则 target 在闭区间 [i, m - 1]中,因此执行 j = m - 1;
	若 nums[m] = target ,则右边界 right 在闭区间 [m+1, j] 中;左边界 leftleft 在闭区间 [i, m-1]中。因此分为以下两种情况:
		若查找 右边界 right ,则执行 i = m + 1 ;(跳出时 i 指向右边界)
		若查找 左边界 left ,则执行 j = m - 1;(跳出时 j 指向左边界)
返回值: 应用两次二分,分别查找 right和 left ,最终返回 right - left - 1即可。
class Solution {
    public int search(int[] nums, int target) {
        // 搜索右边界 right
        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;
        }
        int right = i;
        // 若数组中无 target ,则提前返回,这一行代码可有可无。
        if(j >= 0 && nums[j] != target) return 0;
        // 搜索左边界 left
        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;
        }
        int left = j;
        return right - left - 1;
    }
}

优化,以上代码显得比较臃肿(两轮二分查找代码冗余),改为寻找target - 1的右边界。

在这里插入图片描述

class Solution {
    public int search(int[] nums, int target) {
        return findRight(nums, target) - findRight(nums, target - 1);
    }
    int findRight(int[] nums, int tar) {
        int i = 0, j = nums.length - 1;
        while(i <= j) {
            int m = (i + j) / 2;
            if(nums[m] <= tar) i = m + 1;
            else j = m - 1;
        }
        return i;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值