当前位置: 首页 > web >正文

代码随想录算法训练营第一天 | (二分查找类型)704.二分查找 35.探索插入位置 34.在排序数组中查找元素的第一个和最后一个位置

代码随想录算法训练营第一天 | (二分查找类型)704.二分查找 35.探索插入位置 34.在排序数组中查找元素的第一个和最后一个位置

  • 704.二分查找
    • 循环不变量
    • 左闭右闭解法(我喜欢的方法)
    • 左闭右开解法
  • 35.探索插入位置
    • 个人解题困惑
    • 暴力法
    • 二分查找
    • 左开右开
  • 34.在排序数组中查找元素的第一个和最后一个位置
    • 模板做法
    • 左开右开做法 (left, right)
    • 左闭右开做法
  • 收获:

704.二分查找

文档讲解:代码随想录
视频讲解:手撕二分
状态:复习

循环不变量

解决二分问题首先要找到 循环不变量 ,常用的有两种:

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

在解题过程中始终要保持区间合法

左闭右闭解法(我喜欢的方法)

首先,我们有初始化l = 0, r = nums.size() - 1,因为lr均可能是target

	int n = nums.size();int l = 0, r = n - 1;

然后,我们要判断区间,首先根据循环不变量 [left, right] ,可以看出,left = right是合法的,所以循环条件要写成left <= right
接着,我们要写判断,如果nums[mid] > target,那么mid一定不是target,而right可能是target,所以我们得出的结果是r = mid - 1。同理可以分析left。最后没找到即返回-1

	while (l <= r){int mid = l + (r - l >> 1);if (nums[mid] > target) r = mid - 1;else if (nums[mid] < target) l = mid + 1;else return mid;}return -1;

综上我们解决此题

class Solution {
public:int search(vector<int>& nums, int target) {int n = nums.size();int l = 0, r = n - 1; // 定义target在左闭右闭的区间里,[left, right]while (l <= r) { // 当left==right,区间[left, right]依然有效,所以用 <=int mid = l + (r - l >> 1); // 防止溢出 等同于(left + right)/2if (nums[mid] > target) r = mid - 1; // target 在左区间,所以[left, middle - 1]else if (nums[mid] < target) l = mid + 1; // target 在右区间,所以[middle + 1, right]else return mid; // 数组中找到目标值,直接返回下标}return -1; // 没找到target}
};

左闭右开解法

方法同左闭右闭 ,我把不同之初说明一下。
首先,初始化部分,根据循环不变量[left, right),我们可以得出,right一定不是target,所以我们初始化l = 0, r = n
然后,循环条件,left一定不等于right,写成left < right
循环结果,如果nums[mid] > target,由于midright都不能是结果,所以r =mid
综上所述,我们可以写出代码:

class Solution {
public:int search(vector<int>& nums, int target) {int n = nums.size();int l = 0, r = n;while (l < r) {int mid = l + (r - l >> 1);if (nums[mid] > target) r = mid;else if (nums[mid] < target) l = mid + 1;else return mid; }return -1;}
};

35.探索插入位置

个人解题困惑

对最后的返回的值有点模糊

暴力法

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();for (int i = 0; i < n; i++) {if (nums[i] >= target) return i;}return n;}
};

上述方法的时间复杂度是O(n),但依然可以通过

二分查找

我们先设定区间为左闭右闭区间,即[left, right]
最终,target所在的区间范围就是[right, left],这样才能退出循环。所以,我们插入的位置就是left或者right + 1

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();int l = 0, r = n - 1;while (l <= r) {int mid = l + (r - l >> 1);if (nums[mid] > target) r = mid - 1;else if (nums[mid] < target) l = mid + 1;else return mid; // 情况一:如果target下标就是mid,那么就直接返回mid即可}// 情况二:如果target比第一个数还小,最后区间范围是[-1, 0],left是0,返回left// 情况三:如果target在数组范围之中(可相等也可不相等),target所在范围是[right, left],返回left// 情况三:如果target比任何书都大,即[right, left],返回leftreturn r + 1; // 或者 return l;}
};

左开右开

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int n = nums.size();int l = -1, r = n;// (left, right)// 找到第一个大于等于target元素的下标while (l + 1 < r) {int mid = l + r >> 1;// 确定循环不变量 :// nums[l] < target// nums[r] >= targetif (nums[mid] < target) l = mid;else r = mid;}// 循环结束,l + 1 = rreturn r;}
};

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

模板做法

本道题我看代码随想录没有看懂二分查找的思路,故套用之间背的模板解决此题

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();if (n == 0) return {-1, -1};int l = 0, r = n - 1;while (l < r) {int mid = l + r >> 1;if (nums[mid] >= target) r = mid;else l = mid + 1;}if (nums[l] != target) return {-1, -1};else {int start = l;l = 0, r = n - 1;while (l < r) {int mid = l + r + 1 >> 1;if (nums[mid] <= target) l = mid;else r = mid - 1;}int end = l;return {start, end};}}
};

这个模板运用的也是左闭右闭,只不过它的循环条件是l < r,这个最终是收缩到一个点,而mid = l + r >> 1是向下取等,当r = mid时采取这个;mid = l + r + 1 >> 1是向上取等,当l = mid时采取这个。
startend 返回 lr 都是可以的。

左开右开做法 (left, right)

首先我们要初始化,l = -1, r = n
确定循环不变量,找左边界时 nums[left] < targetnums[right] >= target;找右边界时nums[left] <= targetnums[right] > target
最后我们找到lr 的最终位置关系,即 l + 1 = r

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int l = -1, r = n, start, end; // 初始化// 选择全开区间(l, r)// l 和 r之间一定有数,所以循环条件是 l + 1 < rwhile (l + 1 < r) {// 确定循环不变量:// nums[l]<target : l及l的左侧均小于target// nums[r] >= target : r及r的右侧均大于等于targetint mid = l + r >> 1;if (nums[mid] < target) l = mid;else r = mid;}// 循环结束后 left + 1 = right// 此时 nums[left] < target, nums[right] >= target// 所以 right 就是第一个 >= target 的元素下标start = r; // 注意:start一定要在循环外赋值// 如果nums[]为空那么start == n// 如果 target 不在nums[]中if (start == n || nums[start] != target) return {-1, -1};l = -1, r = n; // 重新赋值while (l + 1 < r) {// 循环不变量:// nums[l] <= target// nums[r] > targetint mid = l + r >> 1;if (nums[mid] > target) r = mid;else l = mid;}end = l;return {start, end};}
};
易错:1)初始化l = -1, r = n2)循环不变量找错,要通过循环不变量来确定最终边界是 l 还是 r3)start 和 end 要在循环体外进行赋值

左闭右开做法

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int l = 0, r = n, start, end;// 选择左闭右开[left, right)while (l < r) {int mid = l + r >> 1;// 确定循环不变量:// nums[left - 1] < target// nums[right] >= targetif (nums[mid] < target) l = mid + 1;else r = mid;}// 循环结束,left = right// 此时, nums[l -1] < target  nums[l] = nums[right] >= target// 所以 left就是第一个>=target的元素下标start = l;if (start == n || nums[start] != target) return {-1, -1};l = 0, r = n;while (l < r) {int mid = l + r >> 1;// 循环不变量:// nums[left - 1] <= target// nums[right] > targetif (nums[mid] <= target) l = mid + 1;else r = mid;}// 循环结束left = right// nums[left - 1] <= target  nums[right] > target// 那么left - 1就是第一个 <= target 的元素下标end = l - 1;return {start, end};}
};
注:左闭右开需要仔细分析最后 start 和 end 究竟是什么,通过循环不变量和最终的 l 和 r 的位置进行分析

收获:

学会了二分查找的左闭右开,左闭右闭,左开右开的写法,虽然卡哥没有讲解左开右开,我看灵神的做法,感觉左开右开可能更简单一些,分析的少一点
之前不太清楚到底返回什么,是 l 还是 r 还是 l - 1什么的,现在清楚了,学会用循环不变量进行分析,分析最后 l 和 r 的位置,进而分析最终到底要返回什么
对于左开右开区间,最终l + 1 == r
对于左闭右开区间,最终l == r
对于左闭右开区间,最终l == r + 1

http://www.xdnf.cn/news/20036.html

相关文章:

  • 涨粉14万,100个Coze智能体工作流模版案例:3分钟生成韩非子权谋爆款视频
  • 【C++】在 Windows 系统调用第三方程序(创建进程)
  • 专项智能练习(Photoshop软件基础)
  • mysql高级进阶(存储过程)
  • H3C UIS Cell 3020 G3服务器更换raid卡安装ONEStor记录
  • windows系统服务器测试部署springboot+vue+mysql项目
  • 企业网络安全建设三阶段实战指南
  • 商家自动运营(四)足浴店管理—东方仙盟
  • 一文掌握Redisson分布式锁
  • 【Rhino】【Python】将开放曲面转换为边界线和填充
  • [特殊字符] DA1-13 复习学习笔记
  • 极空间打造 “超级中枢”,从书签笔记到聊天分享,一键全搞定!
  • 非力扣100原题
  • FTL文件格式的原理与应用(AI)
  • AI歌手功能终于上线!Suno AI 带你保存歌曲的灵魂
  • 【教程】2025 IDEA 快速创建springboot(maven)项目
  • spring boot autoconfigure 自动配置的类,和手工 @configuration + @bean 本质区别
  • 硬件开发1-51单片机2-按键、中断
  • 域名不做网站使用,还需要备案吗
  • 这才是真正懂C/C++的人,写代码时怎么区分函数指针和指针函数?
  • Qt + windows + Linux+QtInstallerFramework打包教程
  • RabbitMQ相关知识
  • 基于 STM32N6-AI Image Classification 使用 git bash 命令行示例 LAT1552
  • 单片机点灯
  • 【C++上岸】C++常见面试题目--算法篇(第十八期)
  • 网络:tcp
  • 关于稳定币的一些问答
  • 封装一个redis获取并解析数据的工具类
  • FPGA学习笔记——SDR SDRAM简介
  • 【golang长途旅行第37站】Redis连接池