算法入门——二分查找

目录

   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、总结

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

多多重复,百炼成钢!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【GoWeb框架初探————XORM篇】

1. XORM xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 1.1 特性 支持 Struct 和数据库表之间的灵活映射&#xff0c;并支持自动同步事务支持同时支持原始SQL语句和ORM操作的混合执行使用连写来简化调用支持使用ID, In, Where, Limit, Join, Havi…

java学习笔记2

3 选择结构 3.1 if选择结构 3.1.1 基本if结构 语法if(条件){// 代码块 }执行流程 当if条件为真,执行代码块,否则不执行代码块。 代码 public class Demo1 {public static void main(String[] args) {// 需求: 张浩的考试成绩>90分,奖励一部Iphone6sScanner sc = new S…

mapreduce中的ReduceTask工作机制(Hadoop)

ReduceTask 是 Hadoop 中的一个重要组件&#xff0c;负责对 MapTask 的输出进行合并、排序和归并&#xff0c;最终生成最终的输出结果。 ReduceTask 的工作机制 1. 分组&#xff08;Shuffle&#xff09;阶段&#xff1a; 在分组阶段&#xff0c;ReduceTask 会从多个 Mapper …

第二届 Oceanbase 开发者大会 实录

第二届 Oceanbase 开发者大会 实录 今天很有幸参加了Oceanbase 开发者大会&#xff0c;我是真的我一开始还不知道什么是Oceanbase &#xff0c;直到我开了会才知道。看来真的需要多参加一些这样活动。 会议议程 我们科普一下什么是Oceanbase OceanBase 是阿里巴巴集团推出…

FastChat启动与部署通义千问大模型

FastChat简介 FastChat is an open platform for training, serving, and evaluating large language model based chatbots. FastChat powers Chatbot Arena, serving over 10 million chat requests for 70 LLMs.Chatbot Arena has collected over 500K human votes from sid…

Llama 3 实测效果炸裂,一秒写数百字(附镜像站)

这几天大火的llama 3刚刚在https://askmanyai.cn上线了&#xff01; 玩了一会儿&#xff0c;这个生成速度是真的亚麻呆住。文案写作和代码生成直接爽到起飞&#xff0c;以往gpt要写一两分钟的千字文&#xff0c;llama 3几秒钟就写完了。而且效果甚至感觉更好&#xff1f; 效果惊…

日期相关的题目

日期相关的题目 1. 计算日期到天数转换2. 日期累加3. 打印日期4. 日期差值 1. 计算日期到天数转换 输出示例: 思路&#xff1a;计算前n-1个月的天数在加上这个月的天数。 #include <iostream> using namespace std;int main() {int year, month, day;cin >> yea…

数据结构练习-数据结构概述

----------------------------------------------------------------------------------------------------------------------------- 1. 在数据结构中&#xff0c;从逻辑上可以把数据结构分成( )。 A. 动态结构和静态结构 B. 紧凑结构和非紧凑结构 C. 线性结…

Spring AI Summary

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Spring AI is a project that aims to streamline the development of AI applications by providing abstractions and reusable components that can be easily integrate…

梯度消失/梯度爆炸

梯度消失/梯度爆炸&#xff08;Vanishing / Exploding gradients&#xff09; 梯度消失或梯度爆炸&#xff1a;训练神经网络的时候&#xff0c;导数或坡度有时会变得非常大&#xff0c;或者非常小&#xff0c;甚至于以指数方式变小&#xff0c;这加大了训练的难度。 g ( z ) …

Java学习Go(入门)

下载Go 《官网下载golang》 直接点Download&#xff0c;然后根据你自己的操作系统进行下载&#xff0c;我这里以win10为例 安装go 默认安装到C:\Program Files\Go&#xff0c;这里我们可以选择安装到其他盘&#xff0c;也可以选择默认安装。初学者建议直接一路next。 安装完…

Java发送邮件 启用SSL

使用的maven依赖: <dependency><groupId>com.sun.mail</groupId><artifactId>javax.mail</artifactId><version>1.4.7</version> </dependency> 配置文件mail.properties如下: # 邮箱配置 email.username=your-email@exa…

(助力国赛)美赛O奖数学建模可视化!!!含代码2(箱型图、旭日图、直方图、三元图、平行坐标图、密度图、局部放大图)

众所周知&#xff0c;数学建模的过程中&#xff0c;将复杂的数据和模型结果通过可视化图形呈现出来&#xff0c;不仅能够帮助我们更深入地理解问题&#xff0c;还能够有效地向评委展示我们的研究成果。   今天&#xff0c;承接《可视化代码1》&#xff0c;作者将与大家分享《…

【软考---系统架构设计师】软件架构

目录 1 一、软件架构的概念 二、软件架构风格 &#xff08;1&#xff09;数据流风格​​​​​​​ &#xff08;2&#xff09;调用/返回风格 &#xff08;3&#xff09;独立构件风格 &#xff08;4&#xff09;虚拟机风格 &#xff08;5&#xff09;仓库风格 三、架构…

【数学建模】优劣解距离法Topsis模型(含MATLAB代码)

TOPSIS法&#xff0c;全称 Technique for Order Preference by Similarity to an Ideal Solution&#xff0c;是由C.L.Hwang和K.Yoon于1981年首次提出的 。这是一种多目标决策分析中常用的有效方法&#xff0c;也被称作优劣解距离法 。 TOPSIS法的基本原理是通过检测评价对象与…

如何使用PHPStudy+Cloudreve搭建个人云盘并实现无公网IP远程访问——“cpolar内网穿透”

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

uniapp中scroll-view初始化的时候 无法横向滚动到某个为止

项目需求 实现日历&#xff08;13天&#xff09;默认高亮第六天 并定位到第六 左边右边各六天&#xff08;可以滑动&#xff09; 直接上代码 <template><scroll-view class"scroll-X":show-scrollbar"true" :scroll-x"scrollable":…

Chrome 侧边栏开发示例

前言 最近做项目&#xff0c;需要开发浏览器扩展&#xff0c;但是考虑页面布局兼容性问题&#xff0c;使用了Chrome114开始的侧边栏&#xff0c;浏览器自带的能力毕竟不会出现兼容性问题&#xff0c;不过Chrome123开始&#xff0c;侧边栏居然又可以选择固定右侧扩展栏了&#…

C++的初步知识——命名空间,缺省参数,重载函数

C 首先写一段代码&#xff1a; #include <stdio.h>int main() {printf("Hello world\n");return 0; }这段C语言代码在cpp文件中仍可运行。我们了解C是兼容C语言的&#xff0c;C的关键字中就包含了C语言的关键字和自身的关键字。关于关键字&#xff0c;我们简…

LCR 039

. - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/0ynMMM/ 给定非负整数数组 heights &#xff0c;数组中的数字用来表示…