2024.06.25 刷题日记

704. 二分查找

这种题有多种写法,我认为应该固定一种写法,从而养成自己的编程直觉,不然很容易造成思维混乱。二分查找说难不难,说简单也不简单。最难的点就是处理边界条件,有时候多个等号会通过,少个等号就通过不了关键是还不知道为什么能过。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

这道题目是最简单的二分查找,很经典。面对这样的题目,首先就得明白,自己要在开区间搜索还是闭区间搜索,这对于边界条件的把握很关键

开区间

那就是在[left, right)中查找,此时,right 可以取到 nums 的边界之后,即 right 可以取 nums.size()。一旦决定要用开区间来解决问题,那么边界条件的确定就很关键,要不要加等号?考虑例子 nums = [1], target = 2。此时应该返回 -1。如果加了等号,即 while(left <= right),那么 下次还会继续再次执行 while(){}, 而 mid = 1,就会造成 越界访问

// nums = [1], target = 2
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0, right = nums.size(), mid;while (left <= right) {mid = left + (right - left) / 2;if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid;}return -1;}
};

因此应该形成这种认知:使用开区间搜索的时候,mid 可能会越界,为了防止越界,应该禁止下次访问:即 while(left < right)

class Solution {
public:int search(vector<int>& nums, int target) {int left = 0, right = nums.size(), mid;while (left < right) {mid = left + (right - left) / 2;if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid;}return -1;}
};

闭区间

闭区间搜索是在[left, right]中搜索,mid 的取值是不会越界的,因此 while(left <= right)是没有任何问题的,思路是同时缩进 left、right,直到 left = right:

class Solution {
public:int search(vector<int>& nums, int target) {int left = 0, right = nums.size()-1, mid;while (left <= right) {mid = left + (right - left) / 2;if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid-1;}return -1;}
};

因此我认为应该养成这两种思维中一种,看个人喜欢哪种逼近方式,但必须选一个。我个人喜欢闭区间,这种不会产生越界访问的风险,而且很直观。

35. 搜索插入位置

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

此时,就应该从上一道题目中发展出这道题目的思路了,我自己采用闭区间搜索。

首先这道题目基本功能是搜索一个目标值的索引,因此和上一道题目一致:

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1, mid;while (left <= right) {mid = left + (right - left) / 2; if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid - 1;}return -1;}
};

但是,原题要求找不到的时候,要求返回应该插入的位置,因此在返回到 -1 的位置,返回left。为什么可以这样?

我们应该注意到,找不到的时候,left、right 是什么状态。假设 nums = [1,3,5,6],target = 4

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1, mid;while (left <= right) {mid = left + (right - left) / 2; if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid - 1;}return -1;}
};
  • left = 0,right = 3:mid = 1,nums[mid] = 3 < 4,left = 2;
  • left = 2,right = 3:mid = 2,nums[mid] = 5 > 4,right = 2;
  • left = 2,right = 2:mid = 2,nums[mid] = 5 > 4,right = 1;

退出循环,可以看到,此时 left 的位置是大于 target 的第一个位置,这来源于 if (nums[mid] < target) left = mid + 1;,因此,我们需要在最后 return left

class Solution {
public:int searchInsert(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1, mid;while (left <= right) {mid = left + (right - left) / 2; if (nums[mid] == target)return mid;if (nums[mid] < target)left = mid + 1;elseright = mid - 1;}return left;}
};

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

示例 1:

输入: letters = [“c”, “f”, “j”],
target = “a” 输出: “c”
解释:letters 中字典上比 ‘a’ 大的最小字符是 ‘c’。

示例 2:

输入: letters = [“c”,“f”,“j”], target = “c”
输出: “f”
解释:letters 中字典顺序上大于 ‘c’ 的最小字符是 ‘f’。

示例 3:

输入: letters = [“x”,“x”,“y”,“y”], target = “z”
输出: “x”
解释:letters 中没有一个字符在字典上大于 ‘z’,所以我们返回 letters[0]。

这道题目依然来源于第一道题,首先如果能搜索到,那么只需要返回这个字母的下一个不一样的字母就行:

class Solution {
public:char nextGreatestLetter(vector<char>& letters, char target) {int left = 0, right = letters.size() - 1, mid;while (left <= right) {mid = left + (right - left) / 2;// 如果找到,那么就返回这个字母的下一个不一样的字母就行if (letters[mid] == target) {while (mid < letters.size() && letters[mid] == target) {mid++;}// 如果越界,说明后面没有比它打的,返回第一个if (mid >= letters.size())return letters[0];elsereturn letters[mid];}if (letters[mid] < target) {left = mid + 1;} elseright = mid - 1;}return letters[0];}
};

当然上面代码是有问题的,但是它目前能解决基本问题,接下来继续改造。

前面说过,如果没搜索到,那么 left 此时指向的就是比目标大的第一个位置,因此可以返回 left。但是如果此时是这一张输入:letters = ["x","x","y","y"],target = "z",此时 left 就会越界,此时应该返回 letters[0]。因此:

class Solution {
public:char nextGreatestLetter(vector<char>& letters, char target) {int left = 0, right = letters.size() - 1, mid;while (left <= right) {mid = left + (right - left) / 2;if (letters[mid] == target) {while (mid < letters.size() && letters[mid] == target) {mid++;}if (mid >= letters.size())return letters[0];elsereturn letters[mid];}if (letters[mid] < target) {left = mid + 1;} elseright = mid - 1;}return letters[left%letters.size()];}
};

这样就把整个答案完善了。我认为解决这样的问题,必须记住甚至背过最基本的题型以及解法,然后遇到类似的题目可以从它身上发散,做二分,就是在做边界条件

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

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

这道题目虽然是中等难度的题目,但是还是很简单的,难点就是把握边界条件。首先查找:

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1, mid, pos = -1;while (left <= right) {mid = left + (right - left) / 2;if (nums[mid] == target) {pos = mid;break;}if (nums[mid] < target)left = mid + 1;elseright = mid - 1;}// 接着处理}
};

如果没有找到,那么返回 {-1, -1};如果找到了,那么就很好,我们只需要从这个值向两边查找就行:

if (pos == -1)return {-1, -1};int p1 = pos, p2 = pos; // 搜索边界while (p1 >= 0) {if (nums[p1] == target) {p1--;} elsebreak;}while (p2 < nums.size()) {if (nums[p2] == target) {p2++;} elsebreak;}return {p1+1, p2-1};

因为从 while 中出来时,p1、p2的值一定是超越正确位置一个身位的,因此必须返回 {p1+1, p2-1};。因此整个题目的解法就是:

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1, mid, pos = -1;while (left <= right) {mid = left + (right - left) / 2;if (nums[mid] == target) {pos = mid;break;}if (nums[mid] < target)left = mid + 1;elseright = mid - 1;}if (pos == -1)return {-1, -1};int p1 = pos, p2 = pos; // 搜索边界while (p1 >= 0) {if (nums[p1] == target) {p1--;} elsebreak;}while (p2 < nums.size()) {if (nums[p2] == target) {p2++;} elsebreak;}return {p1+1, p2-1};}
};

现在看看二分难吗?是不是很简单,套路基本就是搜,搜到了处理一下,搜不到再处理一下。

总结:二分查找及其应用

这篇文章详细讲解了二分查找的基本原理、不同变体的使用方法及其在几道经典题目中的应用。以下是对文章内容的总结:

一、二分查找的基本原理

二分查找是一种高效的查找算法,适用于已经排序的数组。其核心思想是每次将查找范围缩小一半,直到找到目标元素或范围为空。其时间复杂度为 O(log n)。

二、开区间与闭区间
  1. 开区间 [left, right)

    • 查找范围是 [left, right),即 right 不包含在查找范围内。
    • 退出循环条件为 while (left < right),防止越界。
    • 适用于需要严格防止越界的场景。
  2. 闭区间 [left, right]

    • 查找范围是 [left, right],即 right 包含在查找范围内。
    • 退出循环条件为 while (left <= right),更直观且不会越界。
    • 适用于大多数查找问题。
三、应用实例
  1. 704. 二分查找

    • 题目:在排序数组中查找目标值,返回其索引,找不到返回 -1。
    • 思路:可以使用开区间或闭区间的方式进行查找,本文推荐闭区间 [left, right] 的方式,便于处理边界条件。
  2. 35. 搜索插入位置

    • 题目:在排序数组中查找目标值的插入位置。
    • 思路:在查找过程中,如果找不到目标值,返回 left,因为 left 最终会指向第一个大于等于目标值的位置。
  3. 744. 寻找比目标字母大的最小字母

    • 题目:在排序字符数组中找到大于目标字符的最小字符。
    • 思路:如果目标字符存在,则返回其下一个不相同的字符;否则,返回 left 指向的字符,注意处理越界情况。
  4. 34. 在排序数组中查找元素的第一个和最后一个位置

    • 题目:在排序数组中查找目标值的第一个和最后一个位置。
    • 思路:先用二分查找找到一个目标值的位置,然后从该位置向两边扩展,找到目标值的边界。注意在退出循环时,边界值需要处理准确。

核心思想

  1. 选择区间类型:根据题目要求和边界条件,选择合适的区间类型(开区间或闭区间)。
  2. 处理边界条件:在编写循环条件和更新索引时,要小心处理边界情况,防止越界。
  3. 扩展查找结果:在找到目标值后,根据题目要求,可能需要进一步处理,如查找边界或插入位置。
  4. 优化性能:二分查找的效率在于快速缩小查找范围,确保算法的时间复杂度为 O(log n)。

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

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

相关文章

SDN在5G网络中的具体作用是什么?

SDN&#xff08;Software-Defined Networking&#xff09;在5G网络中的具体作用主要体现在以下几个方面&#xff1a; 1. **网络切片&#xff08;Network Slicing&#xff09;**&#xff1a;5G网络的一个关键特性是网络切片&#xff0c;它允许运营商根据不同的服务需求创建多个虚…

【华为OD机试B卷】服务器广播、需要广播的服务器数量(C++/Java/Python)

题目 题目描述 服务器连接方式包括直接相连&#xff0c;间接连接。 A和B直接连接&#xff0c;B和C直接连接&#xff0c;则A和C间接连接。 直接连接和间接连接都可以发送广播。 给出一个N*N数组&#xff0c;代表N个服务器&#xff0c; matrix[i][j] 1&#xff0c; 则代表i和j直…

C++ 模板:全特化和偏特化

目录 全特化&#xff08;Full Specialization&#xff09; 偏特化&#xff08;Partial Specialization&#xff09; 特点和使用场景 注意事项 在C中&#xff0c;模板特化&#xff08;template specialization&#xff09;是一种强大的功能&#xff0c;允许对模板进行特定情…

2024最新算法:鳗鱼和石斑鱼优化(Eel and grouper optimizer,EGO)算法求解23个函数,MATLAB代码

一、算法介绍 鳗鱼和石斑鱼优化器&#xff08;Eel and grouper optimizer&#xff0c;EGO&#xff09;是2024年提出的一种智能优化算法&#xff0c;EGO算法的灵感来自海洋生态系统中鳗鱼和石斑鱼的共生相互作用和觅食策略。 参考文献&#xff1a; [1]A. Mohammadzadeh, S. Mi…

玩转数据库索引

1、概述 通常我们要对数据库进行优化&#xff0c;主要可以通过以下五种方法。 计算机硬件调优应用程序调优数据库索引优化SQL语句优化事务处理调优 本篇文章将向大家介绍数据库中索引类型和使用场合&#xff0c;本文以SQL Server为例&#xff0c;对于其他技术平台的朋友也是有…

DDL-表操作-数据类型

一.DDL-表操作-数据类型 MySQL中的数据类型有很多,主要分为三类:数值类型,字符串类型,日期类型。 二.关系表 注意: 无符号和有符号的取值范围不是一样的,无符号需要加上UNSIGNED范围。 BLOB&#xff1a;用来描述二进制数据 TEXT:用来描述字符串 三.定长字符串和变长字符串 c…

【STM32入门学习】学习嵌入式实时操作系统(RTOS)移植uc/OS到stm32F103上

目录 一、建立STM32HAL库工程 1.1实时操作系统 1.2基于HAL库创建工程 二、获取uC/OS-III源码 三、移植准备 3.1复制uC/OS-III文件到工程文件夹 3.2添加工程组件和头文件路径 四、移植修改代码 &#xff14;.1.启动文件修改&#xff1a; &#xff14;.2.app_cfg.h &a…

Java Scanner 类

Java Scanner 类 java.util.Scanner 是 Java5 的新特征&#xff0c;我们可以通过 Scanner 类来获取用户的输入。 下面是创建 Scanner 对象的基本语法&#xff1a; Scanner s new Scanner(System.in);接下来我们演示一个最简单的数据输入&#xff0c;并通过 Scanner 类的 nex…

Scala 中yield 关键字

Scala 中yield 关键字 在 Scala 中&#xff0c;yield 关键字通常与 for 循环结合使用&#xff0c;用于生成一个集合&#xff08;通常是 List、Array 等&#xff09;。它的作用是将循环体中的每次迭代产生的值收集起来&#xff0c;并最终返回一个包含这些值的集合。 下面是一个简…

关于FPGA对 DDR4 (MT40A256M16)的读写控制 4

关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于FPGA对 DDR4 &#xff08;MT40A256M16&#xff09;的读写控制 4一、引言二、DDR4 SDRAM设备中模式寄存器重要的模式寄存…

background 与img标签加载图片的区别

在HTML和CSS中&#xff0c;通过background属性和img标签都可以加载图片&#xff0c;它们各自有一些优势和劣势&#xff1a; 使用 background 属性加载图片的优势和劣势&#xff1a; 优势&#xff1a; 灵活性&#xff1a; 可以通过CSS的background属性设置背景图片&#xff0…

数组中的逆序对

描述&#xff1a; https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/ 思路&#xff1a; 在进行归并排序时&#xff0c;会分成有序的左右两部分&#xff0c;如果左部分出现了大于右部分的数时&#xff08;nums[cur1] > nums[cur2]&#xff09;&…

Javaweb配置tomcat

Tomcat 9版本链接 链接&#xff1a;https://pan.baidu.com/s/1u-eDur5KlqlXM_IM50Ahtg?pwd1njm 提取码&#xff1a;1njm 1、打开idea&#xff0c;创建maven项目 2023版IDEA 2、 目录结构 ps: 如果结果不完整,选中main右键 新建对应的文件夹 3、 web项目设置Tomcat(部署项目…

特征工程与数据预处理全解析:基础技术和代码示例

在机器学习和数据科学的世界里&#xff0c;数据的质量是建模成功与否的关键所在。这就是特征工程和数据预处理发挥作用的地方。本文总结的这些关键步骤可以显著提高模型的性能&#xff0c;获得更准确的预测&#xff0c;我们将深入研究处理异常值、缺失值、编码、特征缩放和特征…

如何阅读一篇学术论文

第一遍 论文的格式实际上常常遵循着固定的结构&#xff0c;这种结构有助于作者整理思路&#xff0c;同时也帮助读者快速定位论文的要点和创新之处。 最常见的错误是拿到一篇论文后立刻开始从头到尾仔细阅读&#xff0c;然后进行详细笔记。这种方法可以全局了解论文的内容&…

我是如何从功能测试转成自动化测试的!

大家好&#xff0c;我是测试君。&#xff08;六哥也行&#xff09; 时间过得好快&#xff0c;不知不觉已经在这个行业摸爬滚打10年多了&#xff0c;也算是测试圈的一枚老兵了。 回想自己在自动化求知路上的种种&#xff0c;依然历历在目&#xff0c;看着自己一步步的走来&#…

剪画小程序:视频文案提取神器:制作爆款视频的第一步!

在这个信息爆炸的时代&#xff0c;视频成为了我们获取知识和娱乐的重要途径。 但有时候&#xff0c;我们想要的不仅仅是观看视频&#xff0c;而是能够将其中精彩的文案提取出来&#xff0c;为自己的创作添砖加瓦。 现在&#xff0c;有一款神奇的工具应运而生&#xff0c;为您…

工作随记-构造调用虚函数

工作随记-构造调用虚函数 代码编译警告原因解决方法示例代码重构示例总结 代码编译警告 Call to virtual method CoordinateConversionTool::Reset during construction bypasses virtual dispatch [clang-analyzer-optin.cplusplus.VirtualCall]这个警告信息来自 Clang 静态分…

Linux-笔记 高级I/O操作

前言 I/O&#xff08;Input/Output&#xff0c;输入/输出&#xff09;是计算机系统中的一个重要组成部分&#xff0c;它是指计算机与 外部世界之间的信息交流过程。I/O 操作是计算机系统中的一种基本操作&#xff0c;用于向外部设备&#xff08;如 硬盘、键盘、鼠标、网络等&am…

https基础概念

目录 1.什么是https 1.1.https概念 1.2.一些术语 2.https如何加密 2.1.使用对称加密 2.2.使用非对称加密 1.什么是https 首先&#xff0c;https不是http的复数形式 1.1.https概念 &#xff08;1&#xff09;产生背景 为什么会出现https&#xff0c;是因为http协议在传…