算法入门——二分查找

news2024/12/25 9:23:47

目录

   1、二分模板

2、习题

1.704.二分查找

 2.35.搜索插入位置

3.744. 寻找比目标字母大的最小字母

 4.69. x 的平方根

5.1351. 统计有序矩阵中的负数 

6.74. 搜索二维矩阵 

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

8.33. 搜索旋转排序数组

9.153. 寻找旋转排序数组中的最小值 

10.154. 寻找旋转排序数组中的最小值 II

3、总结


   1、二分模板

         二分查找可谓是入门的经典算法了,有固定的模板,但是呢每个人的模板又不太一样,为什么呢?

因为就题论题,主要是边界问题,所以我们需要在做题中,打造一个专属于自己的模板,一看就懂,即使是需要做一点小变化,自己写起来也能得心应手,

下面是我自己的二分模板:

int l = 1, r = n;

while (l < r) {
    int mid = l+(r-l)/2;
    if (mid > target)
        l = mid + 1;//因为mid当前这个值不可能等于target了
                    //所以l更新为mid+1,在[mid+1,r]上查找
    else
        r = mid;//因为mid当前这个值可能等于target了
                //所以r更新为mid,在[l,mid]上查找
}
return r;

 这个模板是在 [1,n] 上查找target,跳出循环时 l==r 所以我们最终返回还是r,都是一样的;

这里的 int mid = l+(r-l)/2 我用的是向下调整,但为什么是这个模式呢?

l+(r-l)/2

=l+r/2-l/2

=l/2+r/2

=(l+r)/2

写成(l+r)可能会爆int,就是超出int范围,有点危险,所以写成l+(r-l)/2,就可以避免这个问题;

 了解了二分模板,我们来做几道题练习一下:

2、习题

题目来源均为LeetCode

1.704.二分查找

注意事项:

标准二分查找,套上面我给的模板,最后加个判断条件,如果当前数不是target,返回-1;

int search(int* nums, int numsSize, int target) {
    int l=0,r=numsSize-1;
    while(l<r)
    {
       int mid = l + (r - l) / 2;
        if(nums[mid]<target)
            l=mid+1;
        else
            r=mid;
    }
    if(nums[r]!=target)
        return -1;
    return r;

}

 2.35.搜索插入位置

注意事项;

如果数组中存在target,直接返回其下标,若不存在,我们需要二次判断,如果target大于当前值,则返回当前值的下标+1,如果target<=当前值,则返回当前值的下标;

int searchInsert(int* nums, int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (target > nums[r])
        return r + 1;
    return r;
}

3.744. 寻找比目标字母大的最小字母

注意事项:

这个题目数组是按照非递减排序的,就是可能有重复元素,所以我们就要优化一下我们的二分模板,采用下面的二分模板,可以取到重复元素中最右边的值,然后最后再判断一下target和当前值的关系,所以我们要就题论题,,二分法往往不是一次就能做对的,多次调试次才可以;

char nextGreatestLetter(char* letters, int lettersSize, char target) {
    int l = 0, r = lettersSize - 1;
    // target比所有目标值都大,返回letters[0]
    if (target >= letters[r])
        return letters[0];
    // 二分查找
    while (l < r) {
        int mid = l + (r - l + 1) / 2; // 向上取整
        if (letters[mid] > target)
            r = mid - 1;
        else
            l = mid;
    }
    // 如果target大于等于当前值,那么就返回当前值的下一个
    if (target >= letters[r])
        return letters[r + 1];
    // 如果target小于当前值,就返回当前值
    return letters[r];
}

 4.69. x 的平方根

注意事项:

用二分法解决数学问题,还是那个模板,本质是不变的,但是我们要考虑数学问题 

int mySqrt(int x) {
    //如果x==1,直接返回1
    if (x == 1)
        return 1;
    //要开 long long类型,不然会超出范围
    long long l = 1, r = x;
    while (l < r) {
        long long mid = l + (r - l) / 2;
        if (mid * mid < x)
            l = mid + 1;
        else
            r = mid;
    }
    //返回的r要么r*r刚好等于x,直接返回r就好了,要么大于x,所以要返回r-1
    if (r * r == x)
        return r;
    return r - 1;
}

5.1351. 统计有序矩阵中的负数 

 注意事项:

相信做到这里,你已经对二分查找有了不少理解吧,那么这道题可能对你来说不难。难的是格式的掌控,不太明白矩阵(也就是二维数组)的格式书写,当我们掌握了格式,对每一行的数据进行二分查找,就很简单了。

int binarysearch(int** grid, int i, int col) {
    int l = 0, r = col-1;
    while (l < r) {
        int mid = (l + r) / 2;
        if (grid[i][mid] >= 0)
            l = mid + 1;
        else
            r = mid;
    }
    if (grid[i][r] >= 0)
        return 0;//如果没有负数,返回0
    return col - r;//返回负数的个数
}
int countNegatives(int** grid, int gridSize, int* gridColSize) {
    // row是行  col是列
    int row = gridSize, col = *gridColSize;
    int sum = 0;
    for (int i = 0; i < row; i++) {
        sum += binarysearch(grid, i, col);
    }
    return sum;
}

6.74. 搜索二维矩阵 

 注意事项:

按照我的思路,先判断target是否存在矩阵中,如果存在,判断最后一列的每一行,确定target存在哪一行,然后在这一行中找到target,如果找不到,返回false;

bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize,
                  int target) {
    int row = matrixSize;
    int col = *matrixColSize;
    //判断target不属于矩阵
    if (target > matrix[row - 1][col - 1] || target < matrix[0][0])
        return false;
    //判断target是在第r行还是第r+1行
    int l = 0, r = row - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (matrix[mid][col - 1] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (target > matrix[r][col - 1])
    // r+1行查找
    {
        int l1 = 0, r1 = col - 1;
        while (l1 < r1) {
            int mid = l1 + (r1 - l1) / 2;
            if (matrix[r + 1][mid] < target)
                l1 = mid + 1;
            else
                r1 = mid;
        }
        if (matrix[r + 1][l1] == target)
            return true;
    } 
    //在第r+1行查找
    else {
        int l2 = 0, r2 = col - 1;
        while (l2 < r2) {
            int mid = l2 + (r2 - l2) / 2;
            if (matrix[r][mid] < target)
                l2 = mid + 1;
            else
                r2 = mid;
        }
        if (matrix[r][l2] == target)
            return true;
    }
    return false;
}

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

注意事项:

题上要求用O(logN)的时间复杂度,我们就知道是用二分了,我的思路是先malloc一个数组,并初始化两个元素都为-1,然后查找两个目标值,用两个二分查找,不过两个二分查找的写法有点差别,但当然这两种写法,我们前面做的题都用过了,相信大家画图理解一下也是可以的;

int* searchRange(int* nums, int numsSize, int target, int* returnSize) {
    // 初始化arr
    int* arr = malloc(sizeof(int) * 2);
    *returnSize = 2;
    arr[0] = -1;
    arr[1] = -1;
    // 数组为空的情况
    if (numsSize == 0)
        return arr;
    // 查找目标值的开始位置
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] < target)
            l = mid + 1;
        else
            r = mid;
    }
    if (nums[r] == target) {
        arr[0] = r;
    }
    // 查找目标值的结束位置
    l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l + 1) / 2; // 向上取整,避免死循环
        if (nums[mid] <= target)
            l = mid;
        else
            r = mid - 1;
    }
    if (nums[r] == target) {
        arr[1] = r;
    }

    // 数组中不存在目标值,直接返回arr
    return arr;
}

8.33. 搜索旋转排序数组

注意事项:

一开始我也没有想出来,看的题解,总结一下就是比较复杂的二分(这里的复杂是比较难想,逻辑性比较强,二分的本质是不变的),注释写的比较详细,大家可以结合示例看看,要是不理解的地方可以在评论区留言呀!

int search(int* nums, int numsSize, int target) {
    int l = 0, r = numsSize - 1;
    while (l <= r) {//如果数组中存在目标值,最终l==r,返回mid
        int mid = l + (r - l) / 2;//取中点mid,防止 l+r 爆int
        if (nums[mid] == target)
            return mid;

        //[l,mid]在前半部分
        if (nums[l] <= nums[mid]) {
            if (target >= nums[l] && target < nums[mid])
                r = mid - 1;//如果target在前半部分,更新r的位置
            else
                l = mid + 1;//如果target在后半部分,更新l的位置
        }
        //[mid+1,r]在后半部分
        else {
            if (target > nums[mid] && target <= nums[r])
                l = mid + 1; //如果target在后半部分,更新l的位置
            else
                r = mid - 1;//如果target在前半部分,更新r的位置
        }
    }
    //如果数组中不存在目标值,返回-1
    return -1;
}

9.153. 寻找旋转排序数组中的最小值 

注意事项:

看代码注释!

两种方法:

法一:找到最大值,然后最大值的后面就是最小值

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    //假设该数组经过n次旋转还是有序的,返回数组的最小值,也就是数组的首元素
    if(nums[l]<=nums[r])
    return nums[l];
    //注意这个地方是nums[mid]和nums[l]比较,并且mid是向上取整的
    //这种方法是找到数组的最大值,然后再最大值的下标+1,得到最小值
    while (l < r) {
        int mid = (r+l+1) / 2;
        if (nums[mid] > nums[l])
            l = mid;
        else
            r = mid-1;
    }
    return nums[r+1];
}

 法二:直接找最小值

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = (r + l) / 2;
        if (nums[mid] > nums[r])
            l = mid + 1;
        else
            r = mid;
    }
    return nums[r];
}

就是两种方法的边界处置不太一样;

10.154. 寻找旋转排序数组中的最小值 II

注意事项:

这道题和上一道题比,多了重复的元素, 解法就是当nums[mid]==nums[r]时,--r,就可以巧妙的解决这个问题,你问我怎么知道的,多次实践+看题解,题解里面大佬多,就会了;

int findMin(int* nums, int numsSize) {
    int l = 0, r = numsSize - 1;
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] > nums[r])
            l = mid + 1;
        else if (nums[mid] == nums[r])
            --r;
        else
            r = mid;
    }
    return nums[r];
}

3、总结

二分查找经典并不简单,要从实践中总结经验,还是要多练,菜就多练

多多重复,百炼成钢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1613054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

政企版 WPS Pro 专业版注册安装教程

政企版 WPS Pro 专业版安装及激活步骤 第 1 步&#xff1a;下载压缩包&#xff08;内含注册码&#xff09;【无解压密码】。 第 2 步&#xff1a;解压缩后&#xff0c;运行 exe 文件&#xff0c;默认步骤安装即可。 第 3 步&#xff1a;安装完成后&#xff0c;新建一个 Word …

【ThinkPHP框架教程·Part-04】URL访问模式

文章目录 一、URL解析1、URL解析格式2、URL解析示例说明3、设置URL重写 二&#xff0e;URL 兼容模式 本章节我们来简单了解一下 ThinkPHP6.0 的 URL 访问模式&#xff0c;解析它的访问方法。 一、URL解析 ThinkPHP 框架非常多的操作都是通过 URL 来实现的。 1、URL解析格式 由…

利用RFID无线射频技术,实现商品防伪和溯源信息管理

近几年来&#xff0c;伴随着信息化产业的快速发展&#xff0c;企业对于产品在生产、流通、分销和零售等环节的实时跟踪和监管的需求日趋强烈。同时企业在经营过程中不可避免地要在不同区域实行差异化的经销商拿货价格&#xff0c;从而导致窜货现象时有发 生&#xff0c;为企业带…

二叉树链式结构的实现-二叉树的前序 中序 后序 层序遍历

一、二叉树的结构了解 二叉树是&#xff1a; 空树非空&#xff1a;根节点&#xff0c;根节点的左子树、根节点的右子树组成的。 前序&#xff1a; 根 左子树 右子树 --》先根 中序&#xff1a;左子树 根 右子树 --》中根 后序&#xff1a;左子树 右子树 根 --》后根 层序&…

命理八字之答案之书前端uniapp效果实现

#uniapp# #答案之书# 不讲废话&#xff0c;先上截图 <div class"padding"><div class"flex align-center justify-center" style"padding-top:100px;"><div class"radarContainer"><div id"radarBox"…

C#医学实验室/检验信息管理系统(LIS系统)源码

目录 检验系统的总体目标 LIS主要包括以下功能&#xff1a; LIS是集&#xff1a;申请、采样、核收、计费、检验、审核、发布、质控、耗材控制等检验科工作为一体的信息管理系统。LIS系统不仅是自动接收检验数据&#xff0c;打印检验报告&#xff0c;系统保存检验信息的工具&a…

关于Android绘制这一遍就够了

Android绘制基础 Android平台提供了一套完整的UI框架&#xff0c;其中包括了绘制组件和绘制API。在Android中&#xff0c;绘制主要涉及到两个核心概念&#xff1a;Canvas和Paint。 Canvas Canvas是Android中的一个类&#xff0c;它代表了绘图的画布。你可以在这个画布上进行…

CAS Client使用以及执行原理

CAS Client使用以及执行原理 流程介绍 CAS Client是利用Java Web中的Filter进行实现认证功能&#xff0c;客户端对CAS Server的认证流程分为以下步骤&#xff1a; 访问CAS Client服务 由于当前session中未检测到认证信息&#xff0c;会重定向到CAS Server地址进行认证 在CA…

11.Ribbon负载均衡策略及修改

ZoneAvoidanceRule 默认使用的规则 修改规则 第一种方式&#xff1a;定义IRule的Bean,作用于全局。 SpringBootApplication MapperScan("com.xkj.org.mapper") public class OrderApplication {public static void main(String[] args) {SpringApplication.run(Ord…

设计模式——2_A 访问者(Visitor)

文章目录 定义图纸一个例子&#xff1a;如何给好奇宝宝提供他想知道的内容菜单、菜品和配方Menu(菜单) & Cuisine(菜品)Material(物料、食材) 产地、有机蔬菜和卡路里Cuisine & Material 访问者VisitorCuisine & Material 碎碎念访问者和双分派访问者和代理写在最后…

C#基础|StringBuilder字符串如何高效处理。

哈喽&#xff0c;你好&#xff0c;我是雷工。 字符串处理在C#程序开发中是使用频率比较高的&#xff0c;但常规的字符串处理方式对内存占用比较多&#xff0c;为了优化内存&#xff0c;减少不必要的内存浪费&#xff0c;引入了StringBuilder类。 下面学习下StringBuilder类的使…

插入排序动态展示3(Python可视化源代码)

修改了“开始”命令按钮&#xff0c;每次单击“开始”&#xff0c;都重新排序。 Python代码 import tkinter as tk import random import timeclass InsertionSortVisualizer:def __init__(self, root, canvas_width800, canvas_height400, num_bars10):self.root rootself.…

wasm 系列之 WebAssembly 和 emscripten 暴力上手

wasm 是什么&#xff1f; wasm 是 WebAssembly 的缩写。wasm 不是传统意义上的汇编语言&#xff0c;而是一种编译的中间字节码&#xff0c;可以在浏览器和其他 wasm runtime 上运行非 JavaScript 类型的语言&#xff0c;只要能被编译成 wasm&#xff0c;譬如 kotlin/wasm、Rus…

鸿蒙OpenHarmony【轻量系统编写“Hello World”程序】 (基于Hi3861开发板)

编写“Hello World”程序 下方将通过修改源码的方式展示如何编写简单程序&#xff0c;输出“Hello world”。请在下载的源码目录中进行下述操作。 前提条件 已参考鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到…

编写函数fun,它的功能是:根据以下公式求P的值,结果由函数值带回。m与n为两个正整数且要求m>n

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 编写…

【JavaEE初阶系列】——网络层IP协议(地址管理和路由选择)

目录 &#x1f6a9;网络层 &#x1f388;IP协议 &#x1f469;&#x1f3fb;‍&#x1f4bb;IP协议"拆包组包"功能 &#x1f388;地址管理 &#x1f469;&#x1f3fb;‍&#x1f4bb;IP地址的分类 &#x1f469;&#x1f3fb;‍&#x1f4bb;NAT机制如何工作的…

记录:阿里云服务器网站搭建(2)

Docker安装Mysql mysql版本 查看开发环境中mysql版本 &#xff1a;select version()&#xff1b;安装时版本尽量保证一致&#xff0c;最低要求大版本要一致 docker 拉取mysql镜像 docker pull mysql:8.0.36 docker启动mysql容器 docker run -d \ # 创建并运行一个容器&…

下班族张亮的副业赚钱故事

张亮是一个普通的上班族&#xff0c;每天过着朝九晚五的生活。他渴望改变现状&#xff0c;却又觉得生活缺乏突破口。直到有一天&#xff0c;他在网络上偶然发现了水牛社这个平台&#xff0c;这为他打开了一扇新的大门。 张亮开始利用下班后的空闲时间&#xff0c;认真浏览水牛社…

IDEA下载与安装

1.下载 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;7v5q 2.安装

综合案例(前端代码练习):猜数字和表白墙

目录 一、猜数字 html代码&#xff1a; 点击 猜 按钮的js代码&#xff1a; 点击 重开游戏 按钮的js代码&#xff1a; 整体代码&#xff1a; 页面效果&#xff1a; 二、留言板 css代码&#xff1a; html代码&#xff1a; js代码&#xff08;主逻辑在这&#xff09;&am…