首页 > 编程语言 >二分查找:手拿把掐!------Java代码实现

二分查找:手拿把掐!------Java代码实现

时间:2024-09-04 19:25:58浏览次数:4  
标签:二分 right Java target nums int mid ------ left

“没有天赋,那就不断重复.”

文章目录


前言

二分真是个好东西,她总是让我在清醒与糊涂间徘徊.
为了加深自己的记忆和印象,特此梳理了之前学习时的md笔记,又找出来回顾了一遍.
今天分享给大家,有好的想法和建议可以在评论区讨论或者私信我.
那么话不多说.我们来进入今天的学习吧!


文章有误敬请斧正 不胜感恩!

提示:以下是本篇文章正文内容,

模板一:(最基本的)

左闭右闭: [left,right]
while(left<=right)
class Solution {
  public int search(int[] nums, int target) {
	 int left=0;
	 int right=nums.length-1;
     int mid;
   	while(left<=right){
      mid=left+(right-left)/2;   //防止越界
   	 if(nums[mid]>target)
       {
         right=mid-1;//更新右边界nums[mid]已经确定了不是要查找的值,所以不必包含mid所以right更新为mid-1;
         //接下来搜索的区间是[left,mid-1];如果写成mid可能就死循环了.
        } 
       else if(nums[mid]<target){
         left=mid+1; //left同理     
        }
       if (nums[mid]==target)
          {
             return mid;
           }    
       }
      return -1;
      }
 }

模板 #1 是二分查找的最基础和最基本的形式。这是一个标准的二分查找模板,大多数高中或大学会在他们第一次教学生计算机科学时使用。模板 #1 用于查找可以通过访问数组中的单个索引来确定的元素或条件。

初始条件:left = 0, right = length-1
终止:left > right
向左查找:right = mid-1
向右查找:left = mid+1

为什么要写成mid=left+(right-left)/2,而不是mid=(left+right)/2。
因为会溢出!!此时的溢出指的是,mid可能会超出该数据类型的最大值
我们假定一个数据类型

uint8           //数据范围0~255
uint8 left,right,mid;

//假定
left = 200;
right = 250;

//则left+right =450 > 255,此时已经溢出了
//0001 1100 0010 因为只能存储8位,实际1100 0010=194
mid = (left+right)/2;  //此时实际mid=194/2

mid = left+(right-left)/2; //200+(250-200)/2 = 225
//此方法绝对不会溢出,最好写成这样


手打:

class Solution {

  public int search(int[] nums, int target) {
	int left=0;
	 int right=nums.length-1;
	int mid;
   	while(left<=right){
     mid=left+(right-left)/2;
   	 if(nums[mid]>target)
     {
         right=mid-1;
      }
    else {
        
         left=mid+1;
        
     }
      if (nums[mid]==target)
      {
           return mid;
       }    
    }

      return -1;

      }
    }

模板二:

左闭右开区间模板:

区间:左闭右开[left,right):

此时left=right在区间上不合法,所以

while(left<right)
class Solution {

  public int search(int[] nums, int target) {
	int left=0;
	int right=nums.length;//注意这一点因为右边是开区间所以right=nums.length时[left,right)  右边界还是从数组最后一项也就是nums.length-1开始的.
	int mid;
   	while(left<right){
     mid=left+(right-left)/2;
   	 if(nums[mid]>target)
     {
         right=mid;//由于if判断过了nums[mid]不符合题意,下一个搜索的左区间不包含mid 又因为右边是开区间 所以right=mid就可以了.
      }
    else if(nums[mid]<target){
         left=mid+1;
     }
      if (nums[mid]==target)
      {
           return mid;
       }    
    }

      return -1;

      }
    }

模板三:

开区间模板:

(left,right)

int left=-1;
int right=nums.length;
int mid;
while(left+1<right){
   mid=left+(right-left)/2;
    if(nums[mid]>target){
        right=mid;
    }
    else if(nums[mid]<target){
        left=mid;
    }
    if(nums[mid]==target){
        return mid;
    }
    return -1;
}

循环不变量:

区间的定义决定了边界处理:

二分查找易错点:

什么时候写left<=right?

什么时候写left<right?
if(nums[mid]>target){

什么时候写right=mid-1?

什么时候写right=mid?

}

做题经验:

如果题目要求是问某个值在不在区间里,用左闭右闭区间(while l<= r),每个mid做大于、小于、等于三次判断,在等于时输出,循环结束未输出说明不在区间里;

如果题目要求“找到第一个大于/小于x的下标”,用左闭右开区间(while l < r),每个mid做大于、小于等于两次判断,不在循环体里输出,循环结束返回l或r(l=r,不要返回mid),就是所求下标。

疑问及解答:

我自己的一些问题:

1、为什么要有区间这个概念
2、为什么要确定右开还是右闭
3、中点怎么计算
4、中点防溢出怎么推导出来的
5、边界问题怎么处理
6、中点取偏左还是偏右

ans:

1、 因为二分查找的本质是在不断地变换区间里比较,并找到target
2、 因为在比较的过程中,会有一个对边界的处理,如果右边的边界参予比较就是闭,如果不参与就是开
3、 M - L = R - M 所以 M = (R + L ) /2
4、 L + (R - L)/2
5、 target < nums[mid]
当区间是右闭的时候,R参予比较的。M与target做了比较,再充当R时需要减1,所以新的 R = M - 1
当区间是右开的时候,R是不参予比较的。R = M
6、取偏左。当数组长度为 1 时,
nums = 【2】
L= 0, R = 1, M =1(右开后,取偏右)
因为 当区间是右开的时候,R是不参予比较,M永远都比较不出值

题目中的:>,>=,<,<=情况的判断:(难点)

模板1:闭区间:找第一个>=targe的元素的下标

int left=0;
int right=nums.length-1;
int mid;
while(left<=right){
   mid=left+(right-left)/2;
   if(nums[mid]<target)
      left=mid+1; 
    else 
        right=mid-1;
    return left;
}

模板2:左闭右开区间:

int left=0;
int right=nums.length;
int mid;
while(left<right){
   mid=left+(right-left)/2;
    if(nums[mid]>target){
        right=mid;
    }
    else if(nums[mid]<target){
        left=mid+1;
    }
    if(nums[mid]==target){
        return left;//return left或者right都可以;因为跳出循环的条件是left==right
    }
    return -1;
}

模板3:开区间:

int left=-1;
int right=nums.length;
int mid;
while(left+1<right){
   mid=left+(right-left)/2;
    if(nums[mid]>target){
        right=mid;
    }
    else if(nums[mid]<target){
        left=mid;
    }
    if(nums[mid]==target){
        return right;
    }
    return -1;
}

//对于lower_bound3
1. 为什么left = -1,int right = nums.size(),而不是left = 0,int right = nums.size()-1?
答案:left = -1是为了mid能取到0。处理【8,8,8,8】 target=8的情况。
        nums.size()-1是为了mid能取到nums.size()。例如【7,7,7,7】 target=8的情况。
2. 为什么终止条件是left + 1 < right或者left + 1 != right?如果nums【mid】 >= target改变为nums【mid】 > target,终止条件应该变化吗?
答案:终止条件取决于nums【mid】 >= target,最终的答案的正确性与left和right的初始化值也有关。
3. start == nums.size() || nums【start】 != target是在处理哪些特殊情况?
答案:【7,7,7,7】 target=8 和 【3,4,6,7】 target = 5 
以上三个细节,环环相扣,例如【7,7,7,7】 target=8贯穿这三个细节。一定要自己输入例子,打印输出,慢慢理解这些细节,才算真正掌握了。就算我把这三个细节列出来,有时候也不一定就能理解,一定要自己从头到尾把细节扣一遍!!!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

在力扣游荡了也近一年了,看到各种天才,窥镜而自视,又弗如远甚.
像我这种没有天赋的人,二分都要研究好久的人,就只能靠不断地重复了.
“贯穿这三个细节。一定要自己输入例子,打印输出,慢慢理解这些细节,才算真正掌握了。就算我把这三个细节列出来,有时候也不一定就能理解,一定要自己从头到尾把细节扣一遍!!!”
以后再看到会不会觉得可笑.

今天的分享就到这里了,我们下篇文章再见.

标签:二分,right,Java,target,nums,int,mid,------,left
From: https://blog.csdn.net/2301_79175212/article/details/141899281

相关文章

  • JAVA基础之四-函数式接口和流的简介
    自从J8开始,对于开发JAVAEE应用的工程师而言,函数式接口会常常接触,某种程度上有点不可绕过。这是因为在绝大部分企业中都会使用Spring来开发JAVAEE,而Spring在它的实现中越来越多地使用上函数式编程。如果我们阅读它的源码,函数式编程是绕不过去的。 函数式编程有其好处,这个好处......
  • 安装Android Studio及第一个Android工程可能遇到的问题
    AndroidStudio版本众多,电脑操作系统、电脑型号、电脑硬件也是多种多样,幸运的半个小时内可以完成安装,碰到不兼容的电脑,一天甚至更长时间都无法安装成功。Android安装及第一个Android工程分为4个步骤,为什么放到一起讲,因为只有Android的第一个工程运行到虚拟机上,Android的开......
  • L1-101 别再来这么多猫娘了! 分数 15
    占位符不要用%#*^之类的,测试点5和7会被卡#include<bits/stdc++.h>usingnamespacestd;voidprint(strings){for(autosi:s)if(si=='{')cout<<"<censored>";elsecout<<si;cout<<endl;}i......
  • 巨量千川:引领服饰品牌破局增长的新引擎
    在当今时代,电商市场犹如一片波澜壮阔的海洋,充满了机遇与挑战。随着科技的飞速发展和消费者需求的不断变化,服装行业正站在一个关键的十字路口,面临着前所未有的困境与考验。那么,究竟如何在这复杂多变的市场环境中开辟出一条新的增长之路呢?让我们一同深入探讨,寻找答案。一、服饰......
  • css常见布局
    两列布局1、flex2、float3、position:absolute三列布局1、flex2、float(圣杯布局,双飞翼布局)3、position:absolute圣杯布局1、注意html结构是main->left->right把重要的内容放在前面,有利于seo2、父级padding3、三个元素都是float<divclass="container"> <div......
  • 从初识Redis到精通Redis,一份Java程序员必备Redis实战文档分享
    本文深入浅出的介绍了Redis的五种数据类型,并通过多个实用示例展示了Redis的用法。除此之外还讲述了Redis的优化方法和扩展方法。一共由三个部分组成,第一部分对Redis进行了介绍,说明了Redis的基本使用方法、它拥有的5种数据结构以及操作5种数据结构的命令,并详解了如何使用R......
  • 12.面向对象(4)
    MODULE12 面向对象知道final修饰成员之后特点会使用静态代码块以及知道静态代码块的使用场景会使用匿名内部类一.权限修饰符(一)概述在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容 会有不同的访问权限(1)public:公共的,最高权限,被public修饰的成......
  • 【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error
    问题描述使用Redis-cli连接Redis服务,因为工具无法直接支持TLS6380端口连接,所以需要使用stunnel配置TLS/SSL服务。根据文章(LinuxVM使用6380端口(SSL方式)连接AzureRedis(redis-cli&stunnel):https://www.cnblogs.com/lulight/p/14188279.html),配置stunnel后,始终......
  • C++基础之杂项
    目录思维导图:学习内容:1. Lambda表达式1.1基本概念1.2定义格式1.3常用情况二、异常处理2.1什么是异常处理2.2何时使用异常处理2.3异常处理的格式2.4异常实例2.5构造和析构中的异常 2.6系统提供异常类 三、C++中文件操作3.1文件流对象的介绍3.2关......
  • 力扣SQL仅数据库(1068~1084)
    1068.产品销售分析1需求编写解决方案,以获取Sales表中所有sale_id对应的product_name以及该产品的所有year和price。输入:Sales表:+---------+------------+------+----------+-------+|sale_id|product_id|year|quantity|price|+---------+--------......