看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

这篇具有很好参考价值的文章主要介绍了看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!!

数组的定义

数组是由n(n>=1)个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素,每个元素在n个线性关系中的序号称为该元素的下标,下标的取值范围为数组的维界。
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17由上图可知:

  1. 数组下标都是从0开始的
  2. 数组内存空间的地址是连续的

数组的存储结构

对于多维数组,有两种映射方式,按行优先和按列优先。以二维数组为例,按行优先存储的基本思想是:先行后列,先存储行号较小的元素,行号相等先存储列号较小的元素。
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

算法题(LeetCode 704.二分查找)—(保姆级别讲解)

力扣题目链接
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

分析题目

  1. 处理数据类型为整型数组
  2. 该数组为有序数组即升序数组
  3. 该数组中的元素是不重复
  4. 上述条件中的2和3是我们决定使用二分查找的关键

什么是二分查找?

我们会使用有序索引数组来表示被查找的目标值target可能存在的子数组的大小范围。在查找时,我们先将被查找的目标值和子数组的中间值比较。如果被查找的目标值小于中间值,我们就在左子数组中继续查找,如果大于,我们就在右子数组中继续查找,否则中间值就是我们要找的目标

算法思想(重要)

二分查找其实有两种写法:

  1. 左闭右闭-----------[left, right]
  2. 左闭右开-----------[left, right)

*至于为什么是两种写法后面会阐述,但是在这里我想声明一点,第二种写法本人不推荐,徒增算法难度,我们学习算法的初心不是算法越难越好,而是如何用最简单的算法思想解决我们的问题,这才是最主要的。所以本篇文章不会讨论第二种,如果对第二种写法感兴趣的可以自行探索。 *

第一种写法代码:

// (写法一) 左闭右闭区间 [left, right]
int search(int* nums, int numsSize, int target){
    int left = 0;
    int right = numsSize-1;
    int middle = 0;
    
    while(left<=right) {
        middle = (left+right)/2;  
        if(nums[middle] > target) {
            right = middle-1;
        } 
        else if(nums[middle] < target) {
            left = middle+1;
        } 
        else if(nums[middle] == target){
            return middle;
        }
    } 
    return -1;
}
时间复杂度:O(log n)
空间复杂度:O(1

好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写!

int search(int* nums, int numsSize, int target)

//函数入口参数分别为int* nums, int numsSize, int target,其中nums是int类型的指针,指向了其目标数组,numsSize为数组元素个数,target为数组目标值。

 int left = 0;
 int right = numsSize-1;
 int middle = 0;

//初始化部分,左边界left初始化为0(因为数组下标是从0开始的),右边界right初始化为numsSize-1(因为数组下标是从0开始的,也就是0~numsSize-1),middle初始化为0(也就是计算中间值的下标)

 while(left<=right) 

//熟悉二分查找的都应该知道最终只会有三种情况,分别是left>right,left=right,left>right,当left>right证明该算法已经遍历结束也就代表其目标数组中没有我们要寻找的目标值(target)。

 middle = (left+right)/2;  

//计算中间值下标的公式,举个例子,假设目标数组中有6个元素,即下标为0~5,那么在第一次二分查找时计算middle值为(0+5)/2 = 2,即下标为2对应的middle值与target值比较大小。

if(nums[middle] > target) {
	right = middle-1;
	} 

//根据二分查找的算法思想可知,当中间值大于target值时,意味着target值可能位于[left,middle-1]这个区间内,所以需要将right赋值为middle-1。

else if(nums[middle] < target) {
	left = middle+1;
	} 

//根据二分查找的算法思想可知,当中间值小于target值时,意味着target值可能位于[middle+1,right]这个区间内,所以需要将left赋值为middle+1。

else if(nums[middle] == target){
	return middle;
	}

//根据二分查找的算法思想可知,当中间值等于target值时,意味着当前遍历的middle下标对应的值即为我们所寻找的目标值。

 return -1;

//如果都不满足上述条件,则代表目标数组中没有我们所寻找的目标值。

为了更能让大家了解二分查找的算法思想,作者特意画了一张图供大家观看!!!
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

第二种写法代码(本人不推荐):

// (版本二) 左闭右开区间 [left, right)
int search(int* nums, int numsSize, int target){
    int length = numsSize;
    int left = 0;
    int right = length;	//定义target在左闭右开的区间里,即:[left, right)
    int middle = 0;
    while(left < right){  // left == right时,区间[left, right)属于空集,所以用 < 避免该情况
        int middle = left + (right - left) / 2;
        if(nums[middle] < target){
            //target位于(middle , right) 中为保证集合区间的左闭右开性,可等价为[middle + 1,right)
            left = middle + 1;
        }else if(nums[middle] > target){
            //target位于[left, middle)中
            right = middle ;
        }else{	// nums[middle] == target ,找到目标值target
            return middle;
        }
    }
    //未找到目标值,返回-1
    return -1;
}
时间复杂度:O(log n)
空间复杂度:O(1

//第二种写法相对于第一种写法的主要区别在于右边界right下标的选取,举个例子,初始阶段,left = 0,但是第二种写法中将right下标设置为numsSize,而不是第一种写法的numsSize-1,虽然改动了这个,但是在以后的判断中间值和target值时,会增加一些难度,例如当中间值大于target值时,意味着target值可能位于[left,middle-1]的区间内,但是由于是第二种写法,所以这里就只需将middle值赋值给right值即可,细心一点会发现和middle值小于target值的情况处理是不一样的,所以在实际的编写算法过程中容易犯错,徒增算法难度。所以第二种写法本人不推荐(没必要和自己过不去,懂得都懂)。

算法题(LeetCode 35.二分查找)—(保姆级别讲解)

力扣链接
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

分析题目

  1. 处理数据类型为整型数组
  2. 该数组为有序数组升序数组
  3. 该数组中的元素是不重复
  4. 上述条件中的2和3是我们决定使用二分查找的关键

算法思想(重要)

//版本一 [left, right]左闭右闭区间,版本二 [left, right]左闭右开区间不推荐(不推荐理由见本文章中有介绍)
int searchInsert(int* nums, int numsSize, int target){
    //左闭右开区间 [0 , numsSize-1]
        int left =0;
        int mid =0;
        int right = numsSize - 1;
        while(left <= right){
            mid = left + (right - left) / 2;
            if(target < nums[mid]){
                right = mid -1;
            }else if( target > nums[mid]){
                left = mid + 1;
            }else {
                return mid;
            }
        }
        //数组中未找到target元素
        //target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1
        return right + 1;
}

可以发现35题和704题代码大致相同,唯一一点是最后的找不到目标值,即在本题目中找不到其所需要插入的位置时,需要返回right + 1,接下来我们来细细讲一下找不到目标值的情况。
找不到目标值大致分为三种情况:

  1. 目标值在数组所有元素之前
  2. 目标值插入数组中的位置
  3. 目标值在数组所有元素之后

为了更能让大家了解找不到目标值的三种情况,作者特意画了一张图供大家观看!!!
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

算法题(LeetCode 34.二分查找)—(保姆级别讲解)

力扣链接
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

分析题目

  1. 处理数据类型为整型数组
  2. 该数组为非递减顺序数组

算法思想(重要)

其实本题题目中已经很清楚表示最终需要找到目标值在数组中的开始位置和结束位置即该问题又可转化为寻找左边界和寻找右边界。所以接下来我们分别探讨这两个问题。
寻找左、右边界又需要考虑三种情况,如下所示:
看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

  1. 寻找左边界
int getLeftBorder(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1; 
    int leftBorder = -2; 
    while (left <= right) {
        int middle = left + ((right - left) / 2);
        if (nums[middle] >= target) { 
            right = middle - 1;
            leftBorder = right;
        } else {
            left = middle + 1;
        }
    }
    return leftBorder;
}
  1. 寻找右边界
int getRightBorder(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1; 
    int rightBorder = -2; 
    while (left <= right) { 
        int middle = left + ((right - left) / 2);
        if (nums[middle] > target) {
            right = middle - 1; 
        } else { 
            left = middle + 1;
            rightBorder = left;
        }
    }
    return rightBorder;
}

分析 :

跟前面两个算法我们对比下,其实细心一点会发现最大的改变有两处,分别是:

  1. 增加了leftBorderrightBorder这两个变量,代表边界
  2. 增加了leftBorder = right;和 rightBorder = left;这两个语句。

为了更能让大家了解这两个不同之处,作者特意画了一张图供大家观看!!!

看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

反思:

从前很喜欢一句话,在这里分享给大家:

告诉我,我会忘记!
给我看,我可能记住!
让我参与其中,我才会理解!

所以我就在想如果经过一段时间后,让我重新再做这道题,我还能不能完整写出来?
那么问题来了,其实和前两个算法我的思考在于两个方面,分别是(以寻找左边界为例):

  1. leftBorder = right;(为什么寻找左边界,是right赋值给leftBoder)
  2. if (nums[middle] >= target) {
    right = middle - 1;
    leftBorder = right;}(为什么leftBorder = right;这个语句在这个if语句里,而不是在else语句里)

针对上面两个思考,我给出自己的理解

  1. 寻找左边界的代码最终返回的变量是leftBoder,在该算法执行的过程中可以发现,因为寻找的是左边界问题,所以相当于是right值不断从右往左减少,即leftBoder是随着right值的趋势改变的。(寻找右边界同理)

  2. 看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

主体代码:

左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一
        if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
        // 情况三
        if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
        // 情况二
        return {-1, -1};
    }
private:
     int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }
    int getLeftBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else {
                left = middle + 1;
            }
        }
        return leftBorder;
    }
};

其中针对这行代码做一些解释:

// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};

因为本算法的功能是计算出来的右边界是不包含target的右边界,左边界同理。

结束语

如果觉得这篇文章还不错的话,记得点赞 ,支持下!!!文章来源地址https://www.toymoban.com/news/detail-450784.html

到了这里,关于看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题203.707.206翻转链表) 2023.4.21

    本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!! 力扣题目链接 关于这个算法思想,我在之

    2024年02月04日
    浏览(53)
  • 看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题242有效的字母异位词) 2023.4.25

    本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!! 力扣题目链接 分析题目: 什么叫做字母异

    2024年02月06日
    浏览(43)
  • 看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题704、35、34数组二分查找) 2023.4.17

    本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!! 数组是由n(n=1)个 相同类型 的数据元素

    2024年02月05日
    浏览(49)
  • 看完这篇文章你就彻底懂啦{保姆级讲解}-----(LeetCode刷题142环形链表II) 2023.4.24

    本文章一部分内容参考于《代码随想录》----如有侵权请联系作者删除即可,撰写本文章主要目的在于记录自己学习体会并分享给大家,全篇并不仅仅是复制粘贴,更多的是加入了自己的思考,希望读完此篇文章能真正帮助到您!!! 力扣题目链接 分析题目: 其实本题目中主

    2024年02月05日
    浏览(51)
  • 看完这篇文章你就彻底懂啦{保姆级讲解}-----(I.MX6U驱动UART串口通信) 2023.5.20

    串口是我们在开发过程中最常用到的外设,所以我们必须掌握。 串口驱动初始化部分 好!按照老样子,接下来开始详细讲解每行代码的用处,以及为什么这样写! 串口驱动初始化部分讲解开始: //将IO功能设置为UART1_RXD和UART1_TXD。 //配置UART1_TX_DATA、UART1_RX_DATA的IO属性。 先关

    2024年02月05日
    浏览(108)
  • ai绘画如何使用?看完这篇文章你就知道了

    对于艺术家和创作者来说,AI绘画可以作为一种实用的工具,提供灵感和创意的源泉。它可以分析和学习大量的艺术作品,从中汲取元素和风格,并以独特的方式重新组合和表达。这使得艺术家能够更快速地探索和实验不同的艺术风格,从而推动他们的创造力和艺术表达的边

    2024年02月09日
    浏览(56)
  • ai绘画生成方法有哪些?看完这篇文章你就知道了

    近年来,随着人工智能技术的不断发展和普及,AI 绘画作为一种新兴的艺术创作方式也逐渐被人们所认知。与传统绘画相比,AI 绘画可以通过计算机算法自动生成,具有高效、便捷的特点,同时也能够更好地满足一些特定场景的需求。 在过去,数字艺术家需要通过绘制、建模

    2024年02月15日
    浏览(57)
  • 怎么用ai绘画二次元拍同款?看完这篇文章你就懂了

    在我们的二次元世界里,每一张优质的插图都能够引发无尽的想象和瞬间的心动。而现如今,随着人工智能技术的飞速发展,ai绘画已经成为一个备受瞩目的领域。在使用ai绘画生成二次元作品时,ai绘画二次元描述词就显得相当重要。那么,究竟ai绘画二次元描述词怎么写呢

    2024年02月16日
    浏览(52)
  • 中缀表达式转后缀表达式看完这一篇文章你就懂了

    一、什么是中缀表达式 二、什么是后缀表达式 三、后缀转中缀具体思路 四、代码实现 中缀表达式就是我们常用的算术表达方式,例如 (12+34)*5 ,运算符在两个数的中间,但是对于中缀表达式来说括号和加减乘除使得问题对于计算机非常复杂,为了有效的处理他们,波兰逻辑

    2024年02月08日
    浏览(51)
  • 你真的学懂if语句了嘛,看完这篇文章你一定会让你有所收获,彻底玩转if语句!

    🎬 鸽芷咕 :个人主页  🔥 个人专栏 :《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想,就是为了理想的生活!    🌈 hello! 各位宝子们大家好啊,相信大家都多多少少了解过if语句吧,但是你真的有了解过,所有if语句的细节吗?学完这篇文章你将知道if语句的所有知识

    2024年02月11日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包