【随想录1】寻找指定位置-二分法

寻找指定位置

代码仓库

704. 二分查找

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

二分法

二分法的难点在于其细节而不是其思想,所以学习过程中要明白其思想,知道所有细节,并能够用代码实现出来。

二分法主要有两种写法

left<=right

    public int search(int[] nums, int target) {
        int l=0;
        int r=nums.length-1;
        //因为left == right是有意义的,所以使用 <=
        while (l<=r){
            // 防止溢出
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                return mid;
            }else if(nums[mid]<target){
                // 当前数字一定比targer小,所以下一次查询的范围是  【mid+1,r】
                l=mid+1;
            }else if(nums[mid]>target){
                //当前数字一定比targer大,所以下一次查询的范围是  【l,mid-1】
                r=mid-1;
            }
        }
        return -1;
    }

left<right

public int searchTwo(int[] nums, int target) {
        int l=0;
        // 定义target在左闭右开的区间里,即:[left, right)
        int r=nums.length;
        // 使用<  是因为left == right在区间[left, right)是没有意义的
        while (l<r){
            // 防止溢出
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                return mid;
                //当 nums[mid] 被检测之后,下一步的搜索区间应该去掉 mid 分割成两个区间
                //即 [left, mid) 或 [mid + 1, right)
            }else if(nums[mid]<target){
                //由于判断的区间范围是 【left,right),此时 mid 位置的数是小于target的
                //下一步判断时就可以跳过 mid ,从mid +1 开始
                //判断范围为 [middle + 1, right)
                l=mid+1;
            }else if(nums[mid]>target){
                //由于判断的区间范围是 【left,right),此时 mid 位置的数是大于target的
                //判断范围为【left, middle)
                // 若 r=mid-1 ,则本来 mid 与 l 产生的判断范围被忽略掉了
                r=mid;
            }
        }
        return -1;
    }

关于left<right 中nums[mid]>target 时 r=mid

举个例子

   -1  0   3   5   9   12
   ----------------------
   0   1   2   3   4   5
   target=3
   l=0,r=6
   第一次 mid = (0+6)/2 =3
   num[3] =5>3
   若 r =mid-1
   则 r=3-1=2
   第二次 mid = (0+2)/2=1
   num[1]=0 <3
   l=mid+1 =1+1=2 = r l==r while 循环退出,找不到target了
   但本例子中是有targer=3的
 
   ------------------------
   选用r =mid
   则 r=3
   第二次 mid = (0+3)/2=1
   num[1]=0 <3
   l=mid+1 = 1+1 =2 < r
   第三次 mid = (2+3)/2=2
   num[2]=3  找到了target

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

对于一个数,第一个位置就是找到其在数组中的左侧位置,最后一个位置就是数组中的右侧位置

采用 left<=right 进行二分查找,所以判断区间为[left,right]

寻找左侧边界的二分搜索

5   7   8   8   8   8		9
-------------------------
0		1		2		3		4		5		6


----左边界求解,关于nums[mid]==target的处理

0+6/2=3
arr【3=8=8
r=mid=3

0+3)/2=1
arr[1]=7<8
left=mid+1=2

2+3)/2=2
arr[2]=8=8
r=mid=2

2+2)/2=2
arr[2]=8=8
r=mid=2;
....开始死循环了,所以r在处理时不能等于mid

------重新开始
0+6/2=3
arr【3=8=8-----
r=mid-1=3-1=2  每次将左边界减小

0+2)/2=1
arr[1]=7<8
left=mid+1=2   左边界右移

2+2)/2=2
arr[2]=8=8
r=mid-1=2-1=1;

left>right  跳出循环

返回left

---------左边界求解,关于nums[mid]>target的处理

5   8   8   9   9   9		9
-------------------------
0		1		2		3		4		5		6


0+6/2=3
arr[3]=9>8
r=mid-1=3-1=2

0+2)/2=1
arr[1]=8=8
r=mid-1=1

0+1)/2=0
arr[0]=5<8
left=mid+1=0+1=1

1+1)/2=1
arr[1]=8=8
r=mid-1=0

left>right 跳出循环

返回left

寻找右侧边界的二分搜索

5   7   8   8   8   8		9
-------------------------
0		1		2		3		4		5		6


----右边界求解,关于nums[mid]==target的处理

0+6/2=3
arr【3=8=8
l=mid+1=3+1=4

4+6)/2=5
arr[5]=8=8
left=mid+1=4+1=5

5+6)/2=5
arr[5]=8=8
left=mid+1=5+1=6

6+6)/2=6
arr[6]=9>8
r=mid-1=6-1=5

left>right 跳出循环

返回right



---------右边界求解,关于nums[mid]>target的处理

8   8   9   9   9   9		9
-------------------------
0		1		2		3		4		5		6


0+6/2=3
arr[3]=9>8
r=mid-1=3-1=2

0+3/2=1
arr[1]=8=8
left=mid+1=1+1=2

2+3)/2=2
arr[2]=9>8
r=mid-1=2-1=1

left>right 跳出循环
返回right


以上分析之后写的代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums.length==0 || (nums.length==1&&nums[0]!=target)){
            return new int[]{-1,-1};
        }
        int left = searchLeft(nums,target);
        int right = searchRight(nums,target);
        return new int[]{left,right};
    }

    private int searchRight(int[] nums, int target) {
        int l=0;
        int r=nums.length-1;
        while (l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                l=mid+1;
            }else if(nums[mid]>target){
                r=mid-1;
            }else if(nums[mid]<target){
                l=mid+1;
            }
        }
        return r;
    }

    private int searchLeft(int[] nums, int target) {
        int l=0;
        int r=nums.length-1;
        while (l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                r=mid-1;
            }else if(nums[mid]>target){
                r=mid-1;
            }else if(nums[mid]<target){
                l=mid+1;
            }
        }
        return l;
    }

}

坑1:没有考虑到返回的左右边界会越界


2	2
---
0	1

target=1

(此用例为右边界越界)

0+1)/2=0
arr[0]=2>1
r=mid-1=0-1=-1

left>right  跳出循环
但此时 ritht=-1 数组越界了
所以返回时要判断 right<0 



2	2
---
0	1

target=3

(此用例为左边界越界)

0+1)/2=0
arr[0]=2>1
r=mid-1=0-1=-1

left>right  跳出循环
但此时 ritht=-1 数组越界了
所以返回时要判断 right<0 

修改返回值:

r>=0 ? r : -1;
l<=nums.length-1 ? l : -1;

坑2:万一target不在数组中

返回时进行判断 l 与 r 位置的元素是不是target

r>=0 ? (nums[r]==target ? r:-1) : -1;
l<=nums.length-1 ? ( nums[l]==target ? l:-1) : -1;

完成ac代码

class Solution {
    public int[] searchRange(int[] nums, int target) {
        if(nums.length==0 || (nums.length==1&&nums[0]!=target)){
            return new int[]{-1,-1};
        }
        int left = searchLeft(nums,target);
        int right = searchRight(nums,target);
        return new int[]{left,right};
    }

    private int searchRight(int[] nums, int target) {
        int l=0;
        int r=nums.length-1;
        while (l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                l=mid+1;
            }else if(nums[mid]>target){
                r=mid-1;
            }else if(nums[mid]<target){
                l=mid+1;
            }
        }
        return r>=0 ? (nums[r]==target ? r:-1) : -1;
    }

    private int searchLeft(int[] nums, int target) {
        int l=0;
        int r=nums.length-1;
        while (l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid]==target){
                r=mid-1;
            }else if(nums[mid]>target){
                r=mid-1;
            }else if(nums[mid]<target){
                l=mid+1;
            }
        }
        return l<=nums.length-1 ? ( nums[l]==target ? l:-1) : -1;
    }

}

参考

我写了首诗,让你闭着眼睛也能写对二分搜索

704. 二分查找

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值