34-在排序数组查询某元素连续位置

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

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

示例 2:

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

示例 3:

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

方法一:直接搜索

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        int searchIndex=nums.Length;
        int start=-1;
        int end=-1;
        for(int i=0;i<searchIndex;i++)
        {
            if(nums[i]!=target) continue;
            else if(start==-1) start=end=i;
            else {
                end=i;
            }
        }
        return [start,end];  
    }
}

直球思路,用时52%,内存5%

猜测由于每次都需要完整遍历导致效率不佳

稍微修改,在寻找目标数据后单独进行搜索

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        int searchIndex=nums.Length;
        int start=-1;
        int end=-1;
        int i=0;
        for(i=0;i<searchIndex;i++)
        {
            if(nums[i]==target) {
                start=end=i;
                break;
            }
        }
        while(++i<searchIndex&&nums[i]==target)
        {
            end=i;
        }
        return [start,end];  
    }
}

时间52%,内存14%

方法二:二分法

先通过二分法找到target,再向左向右进行寻找

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        int searchIndex=nums.Length;
        int start=-1;
        int end=-1;
        int left=0;
        int right=searchIndex-1;
        int middle=-1;
        while(right>=left)
        {
            middle=left+(right-left)/2;
            if(nums[middle]==target) break;
            else if(nums[middle]<target) left=middle+1;
            else right=middle-1;
        }
        if(middle!=-1&&nums[middle]==target)
        {   
            int temp=start=end=middle;
            while(--middle>-1&&nums[middle]==target) start=middle;
            while(++temp<searchIndex&&nums[temp]==target) end=temp;
        }
        return [start,end];  
    }
}

时间52% 内存59%

方法三:最优二分法--在搜索的同时直接搜索出区间

对于二分法的理解:二分法主要区分在于对判断条件的理解,第一while区间主要用于循环的判断,即完成对所有可能的遍历,第二个mid与target的判断主要用于是判断target还是判断极限点,

理解:二分法类似于极限逼近,if的两个判断类即两端的极点。当nums[mid]>=target即右端为大于等于target的点,左端为小于的点,而当nums[mid]>target即右端为大于target的点,右端为小于等于的点。,对于mid的取值分为两种即是否加一,这一步骤决定了最后的值是偏左还是偏右

区间为[low,high]的代码

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        int start =SearchStart(nums,target);
        if(start<0){
            return new int[] {-1,-1};
        }
        int end =SearchEnd(nums,target);
        return new int[] {start,end};
        }
    public int SearchStart(int[] nums, int target) {
        int low=0,high=nums.Length-1;
        while(low<=high)
        {
            int mid=low+(high-low)/2;
            if(nums[mid]>=target)
            {
                high=mid-1;
            }
            else low=mid+1;
        }
        return low<nums.Length&&nums[low]==target?low:-1;
    }
    public int SearchEnd(int[] nums, int target) {
        int low=0,high=nums.Length-1;
        while(low<=high)
        {
            int mid=low+(high-low+1)/2;
            if(nums[mid]<=target)
            {
                low=mid+1;
            }
            else high=mid-1;
        }
        return high<nums.Length&&nums[high]==target?high:-1;
    }    
}

区间分别为左开右闭,和左闭右开的代码-leetcode标准答案

public class Solution {
    public int[] SearchRange(int[] nums, int target) {
        int start = SearchStart(nums, target);
        if (start < 0) {
            return new int[]{-1, -1};
        }
        int end = SearchEnd(nums, target);
        return new int[]{start, end};
    }

    public int SearchStart(int[] nums, int target) {
        int low = 0, high = nums.Length - 1;
        while (low < high) {
            int mid = low + (high - low) / 2;
            if (nums[mid] >= target) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }
        return low < nums.Length && nums[low] == target ? low : -1;
    }

    public int SearchEnd(int[] nums, int target) {
        int low = 0, high = nums.Length - 1;
        while (low < high) {
            int mid = low + (high - low + 1) / 2;
            if (nums[mid] <= target) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return low < nums.Length && nums[low] == target ? low : -1;
    }
}

两者均可运行且时间极短

总结

对于二分法来说,有非常多需要注意的点

while(low<=high)                      //行1
        {
            int mid=low+(high-low)/2; //行2
            if(nums[mid]>=target)    //行3
            {
                high=mid-1;          //行4
            }
            else low=mid+1;          //行5
        }

这是一个标准的二分法代码,其中很多地方的切换会导致很多不一样的效果

行1 

描述的是循环的能运行的区间的条件,决定了是否将所有可能值全循环一遍

行二

描述的是mid的取值,其另一种写法为:int mid=low+(high-low+1)/2; 其是否加一,决定了在low和high其中为一奇一偶时,其mid取值偏向于左边还是右边。当加一偏向右,不加偏向左

行三

描述的是 nums[mid] 与 target 的大小对比。我们可以假设此处的if条件为极限逼近,其条件即为逼近两点的极限值。有四种可能,且配合mid的取值,决定获得的是左侧的极限值还是右边

拿上图举个例子,在此时,为 nums[mid]>=target 代表了其极点:右为target,左为小于target的值,而mid为偏向左,即获得便是小于target的最大值,但是由于最后再次进行了一次low=mid+1;所以获得值为target最小值,当然如果你选择先加减,再求mid那么最后就不需要加一,下列代码也是求得target最小值

while(low<=high)
        {
            if(nums[mid]>=target)
            {
                high=mid-1;
            }
            else low=mid+1;
            mid=low+(high-low+1)/2;
        }
行三行四

分别对应着不断缩小的距离,直观决定了某个值无法重复使用

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值