Leetcode二分题组34/35/36


前言

第34题是二分查找类型题目的升级版本

一、题目描述

34:给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。
请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

二、代码思路

1.首次思路(实现遇挫)

算法思路:题解

  1. findFirst:在区间[left,right]里找左边界。对于mid=(left+right)/2,nums[mid]大于/小于的情况不多做解释,和传统二分无区别。在nums[mid]=target处,表示该mid处的target是区间[mid,right]的左边界target元素,故只用搜索[left,mid]区间是否有target,所以更新right=mid。
  2. findLast:在区间[left,right]里找右边界。对于mid=(left+right)/2,nums[mid]大于/小于的情况不多做解释,和传统二分无区别。在nums[mid]=target处,表示该mid处的target是区间[left,mid]的右边界target元素,故只用搜索[mid,right]区间是否有target,所以更新left=mid。
public static int findFirst(int[] nums, int target) {
		int left = 0;
		int right = nums.length-1;
		int mid;
		// [left,right]
		while(left<=right) {
			mid = (left+right)/2;
			if(nums[mid]<target) {
				left = mid+1;
			}
			else if(nums[mid]>target) {
				right = mid-1;
			}
			else {
				right = mid; //错误处!
			}
		}
		if(nums[left]==target) {
			return left;
		}
		return -1;
	}

算法无法实现的原因

  • 该findFirst解法的思路是:在[left,right]区间内,如果nums[mid]=target,则表示可以在[left,mid]区间继续二分搜索,所以“right=mid”。但忽略了一个错误,如果果mid=right,那么算法将陷入死循环。写到这突然意识到,findFirst不会有这个错误,因为mid=(left+right)/2,只会和left值相同(如[4,5],除以2只会是4,所以right的更新不会原地踏步)。这样也就退出了循环。
  • 但在findLast找右边界中会出现错误,更新语句是"left=mid"。比如[4,5]。left = (4+5)/2=4,将陷入死循环,永远是left=mid=4。

2.二次思路(成功实现)

算法思路:题解

  1. 在[left,right]之间寻找target出现的右边界。
  2. 语言描述过程:左边界初始化为ans = -1。对[left,right]区间使用二分法,当出现第一个nums[mid1]=target时,ans=mid1。于是在[mid1+1,right]区间继续使用二分法。当出现第二个nums[mid2]=target时,ans=mid2。于是在[mid2+1,right]区间使用二分法…这就避免出现1种解法的问题。
public static int findLast(int[] nums, int target) {
		int left = 0;
		int right = nums.length-1;
		int mid;
		int ans=-1;
		// [left,right]
		while(left<=right) {
			mid = (left+right)/2;
			if(nums[mid]<target) {
				left = mid+1;
			}
			else if(nums[mid]>target) {
				right = mid-1;
			}
			else {
				ans = mid;
				left = mid+1;//重点
			}
		}
		return ans;
	}

三、35题

题目描述:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

解法1

解法
这种总是分析不明白当target在范围之内,但不是nums中的一个元素的情况。即while(left<=right)退出时,应该return一个什么值,是return right还是return first。

class Solution {
    public int searchInsert(int[] nums, int target) {
        int first = 0;
        int last = nums.length-1;
        int mid = 0;;
        if(target<=nums[0]){
            return 0;
        }
        if(target>nums[last]){
            return nums.length;
        }
        if(target==nums[last]){
            return nums.length-1;
        }//小于等于和大于等于不太一样
        while(first<=last){
            mid = (first+last)/2;
            if(nums[mid]==target){
                return mid;
            }
            else if(nums[mid]<target){
                first = mid+1;
            }
            else{
                last = mid-1;
            }
        }
        return first;//插入的位置
    }
}

解法2(便于理解,思路源于解法1和34题)

算法思路:首先可以分析出35题本质是在nums种寻找第一个大于等于target的元素(类似34寻找左边界)。有一种特殊情况是,当target大于所有元素时。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int firstP = findFirst(nums,target);
        int lastP = findLast(nums,target);
        return new int[]{firstP,lastP};
    }
    //找左边界
    public int findFirst(int[] nums, int target) {
        int ans = -1;
        int left = 0;
        int right = nums.length-1;
        int mid;
        while(left<=right){
            mid = (left+right)/2;
            if(nums[mid]<target){
                left = mid+1;
            }
            else if(nums[mid]>target){
                right = mid-1;
            }
            else{
                ans = mid;
                //相等,则在[left,mid-1]找是否有target
                right = mid-1;
            }
        }
        return ans;
    }
    // 找有边界
    public int findLast(int[] nums, int target) {
        int ans = -1;
        int left = 0;
        int right = nums.length-1;
        int mid;
        while(left<=right){
            mid = (left+right)/2;
            if(nums[mid]<target){
                left = mid+1;
            }
            else if(nums[mid]>target){
                right = mid-1;
            }
            else{
                ans = mid;
                //相等,则在[mid+1,right]找是否有target
                left = mid+1;
            }
        }
        return ans;
    }
}

算法解析:

  1. 区间定在[left,right],故循环条件是while(left<=right)。我们在[left,right]寻找第一个大于等于target的元素。这种方法特点在于多用一个ans来记录,我觉得会更清晰明了,避免死循环和退出循环时难以分析的问题。
  2. ①mid1=(left+right)/2,如果nums[mid]<target,则区间[left,mid]不可能出现大于等于target的元素,故下一次搜索区间为[mid+1,right],故left=mid+1。
    ②如果nums[mid]>target,则mid有可能是第一个大于等于target的元素,首先用ans将其记录(ans=mid),则下一步我们要在[left,mid-1]处来搜索是否有大于等于target的元素。

综上,是一个很好理解的方法b( ̄▽ ̄)d

三、36题(算术平方根)

题目描述:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留整数部分,小数部分将被舍去 。

class Solution {
    public int mySqrt(int x) {
        int left = 1;
        int right = x;
        int mid;
        int ans = 1;
        if(x==0) return 0;
        while(left<=right){
            mid = (left+right)/2;
            if(mid*mid<x){
                ans = mid;
                left = mid+1;
            }
            else if(mid*mid==x){
                return mid;
            }
            else{
                right = mid-1;
            }
        }
        return ans;
    }
}

算法解析:

  1. 相当于在[1,x]之间寻找一个元素最大ans使ans×ans<=x。我们在双向闭区间[left,right]种寻找。mid=(left+right)/2,如果mid×mid<x,则它可能是我们的解,下次在[mid+1,x]之间搜索。如果mid×mid==x,直接返回mid。如果mid×mid>x,则在[left,mid-1]之间搜索。

  2. 注意点:①没看到有0,所以其实是[0,x]。②如果在[0,x]会报超时,需要利用一个数学结论:所有大于等于4的数,算术平方根小于等于它的一半,故搜索区间为[0,x/2]。③注意x的范围,最大为2^31-1,所以mid×mid有可能会溢出,所以改为(long)mid×mid。

备注

1、重新刷题时,有必要把704(传统二分)/35(稍进阶)一起做
更新:704/69很简单,34和35和36可以一起做
2、以后刷题,统一用区间[left,right]

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值