首页 > 其他分享 >【LeetCode数组#1二分查找】二分查找、搜索插入、在排序数组中查找元素的第一个和最后一个位置

【LeetCode数组#1二分查找】二分查找、搜索插入、在排序数组中查找元素的第一个和最后一个位置

时间:2022-12-29 21:12:32浏览次数:65  
标签:二分 right target nums int mid 查找 数组 left

二分查找

题目

力扣704题目链接

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4       
解释: 9 出现在 nums 中并且下标为 4     

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2     
输出: -1        
解释: 2 不存在 nums 中因此返回 -1        

提示:

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

初见思路

虽然这套题有明确的考察点,但是我习惯拿到题先用最笨的办法实现一次,然后再改进算法

这题整体还是比较简单的,如果用常规的思路也很容易做出来

例如:遍历数组,找到符合条件的值,然后返回其下标,如果条件均不满足,则返回默认下标值(-1)

class Solution {
    public int search(int[] nums, int target) {
        int index = -1;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] == target){
                index = i;
            }
        }
        return index;
    }
}

那么实际上,本题考察的主要是“查找”数组的过程

显然,遍历的方式对于二分法来说,是非常低效的,因此肯定是要用二分法来写的

常规思路

在升序数组nums中寻找目标值target,对于特定下标 i,比较nums[i]和target的大小

  • 如果nums[i] = target,则下标 i 即为要寻找的下标;
  • 如果nums[i] > target,则 target 只可能在下标 i 的左侧;
  • 如果nums[i] < target,则target只可能在下标 i 的右侧。

基于上述事实,可以在有序数组中使用二分查找寻找目标值。【ps:如果数组是乱序,那么有你可能会找出多个值】

二分法的解题流程如下:
1、定义一个查找的范围,一般用left和right代表左右边界

2、在该范围内取中点middle,比较nums[mid]和target

3、根据比较结果进行返回或继续对某侧进行查找

解题模板

实际上二分法有两种写法,分别是左闭右闭和左闭右开【即是否包含边界点本身】,模板只记一种就行,另外的补充再说

Java版
class Solution {
    public int search(int[] nums, int target) {
        //①定义左右边界
        int left = 0, right = nums.length - 1;
        //②写一个条件循环*
        while (left <= right) {
            //计算区间的中点mid
            int mid = (right - left) / 2 + left;//用减是为了防止数据溢出,加left是为了定位到当前区间
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) { //此时,target只能在区间中点的左半部分,因此右边界改变,变为左半部分的右边界
                right = mid - 1;
            } else if(nums[mid] < target) {//target只能在区间中点的右半部分,因此左边界改变,变为右半部分的左边界
                left = mid + 1;
            }
        }
        return -1;//找不到返回-1
    }
}

注释:

边界问题

在上述写法中有两处值得关注的边界,

一处是while的条件left <= right【什么时候要写<、什么时候写<=】,一处是边界改变时的mid - 1【什么时候写mid,什么时候写mid-1】

这两个边界问题都只取决于你决定采用哪种二分法的写法,在一开始我们就必须先确定要用左闭右闭还是左闭右开,然后后面再写的时候要贯彻执行,根据区间的定义去选择边界

以左闭右闭[left, right]为例

当忘了while里应该写<=还是<时,可以想一想一开始确定的区间

比如,如果我写left <= right,对于[left, right]来说是不是合法的呢?【[1,1],1到1的区间且包含1,虽然只有一个元素但至少不非法】

显然还是可以这么写的,所以在左闭右闭时应该写left <= right

类似的,left <= right对于[left, right)则不合法【[1,1),即包含1又不含1?】

那么mid呢?

假设目前你的nums[mid] > target,那么这个target一定不会出现在当前mid的右边区间了

所以下一步应该从左边区间再查找

这时候应该将当前的右边界调整到左边区间上来,并且需要排除掉当前的nums[mid]

因此下次查找的右边界的下标应该是right = mid - 1

如果不减1就相当于把一个不属于下次查找区间的数放到区间中了,对于区间来说是非法的,自然会出错

类似的,right = mid - 1对于[left, right)则不合法,因为[left, right)在下次查找中本来就不包含right,所以应该直接写right = mid【若nums[mid] < target,则[left, right)的left还是需要写成left = mid - 1,因为left在[left, right)中是被包括在内的】

Python版
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums) - 1
        while left <= right:
            mid = (right - left)//2 + left
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        return -1

拓展练习

LeetCode35搜索插入位置

思路就是二分查找,这里还是选左闭右闭的方式去写

Java版

class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = (right - left)/2 + left;
            if(nums[mid] == target){
                return mid;
            }else if(nums[mid] < target){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        //此处可以同时处理四种情况下的返回值
        //1、目标值等于数组中某一个元素  return middle;
        //2、目标值插入数组中的位置 [left, right],return  right + 1
        //3、目标值在数组所有元素之后的情况 [left, right],这是右闭区间,所以  return right + 1
        //4、目标值在数组所有元素之前  [0, -1]【没想通】
        return right+1;
    }
}

Python版

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)-1
        while left <= right:
            mid = (right - left)//2 + left
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1 
            else:
                right = mid - 1
                
        return right+1
LeetCode34在排序数组中查找元素的第一个和最后一个位置

标签:二分,right,target,nums,int,mid,查找,数组,left
From: https://www.cnblogs.com/DAYceng/p/17013535.html

相关文章

  • 【LeetCode数组#2移除元素】移除元素、删除有序数组中的重复项、移动0
    移除元素力扣27题目链接(opensnewwindow)给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。不要使用额外的数组空......
  • C语言--数组1
    1、求斐波那契数列的前20项和#include<stdio.h>intmain(){inta[20];a[0]=a[1]=1;intsum=0;for(inti=2;i<20;i++){a[i]=a[i-2]+a[i-1];sum+=a[i];......
  • 二分搜索与二分答案
    二分的本质序列要满足有序性或者有有序的性质:有单调性一定可以二分,没有单调性也可以进行二分;下面是两个模板;tips:mid=男左女右,男加1boolcheck(intx){/*...*/}//......
  • 数组方法 JavaScript
    //连接两个数组consta1=[1,2,3];consta2=[4,5,6];consta3=a1.concat(a2);console.log(a3);console.log("----------------------------------------......
  • 最小堆的数组实现
    1.创建新堆(数组)2.插入新数3.检查该数和其父节点的大小,如果该数较小,交换;重复操作直到不大于或者成为根结点; 例题:将一系列给定数字插入一个初始为空的极小化堆H[]。随......
  • 代码随想录算法训练营第二天 | 977.有序数组的平方、209.长度最小的子数组
    977.有序数组的平方题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/题目给你一个按非递减顺序排序的整数数组 nums,返回每个数字的平方组成的新数组......
  • 第三章《数组与循环》第2节:多维数组
    ​3.1小节介绍的数组都是把数组元素从逻辑上排成一行,因此它们被称为“一维数组”。如果一个班级内有15个学生,这些学生按照身高又分成3排就坐,其排列形式如图3-2所示:3-2学生身......
  • 第三章《数组与循环》第3节:while循环
    ​在实际开发过程中,常常需要计算机完成很多重复性的工作。例如,有一个int型的数组array,它的长度为5,如果程序员希望打印出该数组的每一个元素,可以用如下代码实现:System.out.pr......
  • 第三章《数组与循环》第4节:for循环
    ​3.3小节曾经讲到:Java语言中有3种循环,其中一种是for循环。for循环又可以分为普通for循环和增强型for循环。本小节首先讲解普通for循环的使用。3.4.1普通for循环在3.3小节......
  • 第三章《数组与循环》第5节:do...while循环
    ​前面两个小节中介绍的while循环和普通for循环,都有一个显著的共同特点:执行循环语句之前要先判断循环条件是否成立,仅在循环条件成立的情况下才执行循环语句,这种循环方式可以......