LeetCode——二分(初阶)

LeetCode——二分(初阶)

文章目录

简要介绍

二分算法是我们写算法题目中比较常见的一种优化算法,这个算法很好地体现了分治的思想,我们的二分算法不管是在算法题目中屡见不鲜,在实际工程当中也是十分热门的一个算法,比如我们的数据库索引、文件系统、甚至是我们游戏AI的路径规划都或多或少地用到了我们的二分算法,在我们的算法题目中常见的几种二分算法分别是二分答案、二分区间和二分浮点数。

二分答案

这是一个在有单调性的区间中找值的一个算法,通过二分思想来调整我们的查找范围。

二分区间

这个其实和我们的二分答案类似,只不过这里是搜索的一个区间而不是一个值,这种方法常应用于在区间内查找某一个解的情况。

二分浮点数

这个就比较冷门了,这里其实和二分答案类似,只是这里的处理方式不同而已。

相关例题

二分查找

题目描述

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果 target 存在返回下标,否则返回 -1

你必须编写一个具有 O(log n) 时间复杂度的算法。

示例 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 

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。
题目分析

这个题目是一道相当经典的一道二分答案的题目,我们这里的思想就是二分。

实现思路

定义两个指针,一个左指针left,一个右指针right,分别指向我们的数组的左右端,计算出我们的中间点mid,然后就是三种情况的讨论了:
1、arr[mid] == target说明找到了,直接返回即可。

2、arr[mid] > target说明我们的区间[mid, right]都是要大于我们的target的,所以我们要舍弃这个区间的值,在我们的左边[left, mid - 1]继续查找,也就是让我们的right = mid - 1

3、arr[mid] < target说明我们的区间[left, mid]这个区间的值都是要小于我们的target的,所以我们这里要舍弃这一段区间,在我们的区间[mid + 1, right]继续进行查找,也就是让我们的left = mid + 1

以上三个操作都是在while(left < right)条件下进行的,跳出循环说明没有找到。

实现代码
classSolution{public:intsearch(vector<int>& nums,int target){int n=nums.size();int left=0,right=n-1;while(left<=right){int mid=left+(right-left+1)/2;if(nums[mid]<target) left=mid+1;elseif(nums[mid]>target) right=mid-1;elsereturn mid;}return-1;}};

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

题目描述

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 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] 

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109
题目分析

这个题目就是我们之前说的二分区间的经典例题,这里其实还是比较容易混淆的,这里我们做一个区分(使用基础二分,通过基础二分模板然后保留特定的左或是右下标的方式的做法其实是找到的值不一定是等于我们的target的,这里的就是必须要是等于target的算法)。

实现思路

我们这里的基本思路就是找到我们的左边界和我们的右边界,这是两种不同的情况,我们这里需要分开讨论着两种情况的:

找左边界:

我们这里需要明确几个区间的特点:

我们的左区间[left, k - 1]都是要小于我们的target的。

我们的右区间[k, right]都是要大于等于我们的target的。

所以这里我们的mid的位置右有了下面的两种情况:
当我们的mid[left, k - 1]的时候,也就是说arr[mid] < target,说明[left, mid]都是可以舍弃的,我们这里和我们的二分答案的更新是一样的就是将left移到mid + 1的位置上。

但是当我们这里的mid[k, right]的时候就和我们的二分答案的地方不一样了,这里我们是大于等于我的target值,我们要找的就是tatget值在的最左边的下标,所以这里的k我们是不能舍弃的,我们这里可以将我的[left + 1, right]舍弃掉,所以这里我们可以将right更新到mid位置。

需要特别注意的是(也是特别容易出错的):我们这里一定是向下取整。

示例(如果是向上取整):

我们这里图示的情况就是陷入了死循环的状态,所以这里我们采取的应该是向下取整,这里我们也是比较好理解的,相较于我们的二分答案我们这里的代码主要还是没有让我们的右指针往左靠,于是我们这里采用向下调整整体上是使得我们的mid更加向我们的left靠近了,这也就弥补了右指针没往左靠的问题。

在这里插入图片描述

我们这里分析我们的右边界也是一样的,首先还是要明确我们的区间特点:

左区间[left, k]都是要小于等于target的。

右区间[k + 1, right]都是要大于我们的target的。

我们的mid位置的情况也是两种:

当我们的mid在[left, k]区间的时候,因为这个区间是小于等于我们的target的,所以这里的mid是不能舍弃的,我们这里可以将区间[left, mid - 1]舍弃,于是这里的更新就变成了left = mid

当我们的mid[k + 1, right]的时候,说明我们的[mid, right]都是大于target的,所以这里的区间内的值都是可以舍弃的,于是这里的更新就变成了right = mid - 1

注意:我们这里的区间是要向上取整的。

我们这里还是举出一个向下取整而导致死循环的例子:这里的解释和上面类似。

在这里插入图片描述
实现代码
classSolution{public: vector<int>searchRange(vector<int>& nums,int target){int n=nums.size();if(n==0)return{-1,-1};int left=0,right=n-1;while(left<right){int mid=left+(right-left)/2;if(nums[mid]<target) left=mid+1;else right=mid;}int Left=0;if(nums[left]!=target)return{-1,-1};// 如果找到的下标对应的值不是目标值说明我们整个区间内都没有这个target直接返回{-1, -1}即可else Left=left; left=0,right=n-1;while(left<right){int mid=left+(right-left+1)/2;if(nums[mid]<=target) left=mid;else right=mid-1;}return{Left,left};}};
小技巧
这里给一个取整的小技巧,while里面有-1,我们的取整就要+1,反之则没有+1

搜索插入位置

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 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 

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums无重复元素升序 排列数组
  • -104 <= target <= 104
题目分析

这个题目实际上可以转换成找出大于等于目标值的最左端,这样我们就可以复用之前写的代码了。

实现思路

这里的实现思路就是上面题目中的找左边界的思路,不过这里我们可以右两种写法,也是上面提到的容易混淆的情况,这里我们其实可以使用二分的基础模板,然后标记大于等于目标值的下标即可。

实现代码
代码一
classSolution{public:intsearchInsert(vector<int>& nums,int target){int ret=-1;int left=0,right=nums.size()-1;if(nums[nums.size()-1]<target)return nums.size();while(left<right){int mid=left+(right-left)/2;if(nums[mid]<target) left=mid+1;else right=mid;}return left;}};
代码二
classSolution{public:intsearchInsert(vector<int>& a,int value){int L =0;int R = a.size()-1;if(a[a.size()-1]<value)return a.size();int index =-1;while(L <= R){int mid = L +((R - L +1)>>1);if(a[mid]>= value){ index = mid; R = mid -1;}else{ L = mid +1;}}return index;}};

x 的平方根

题目描述

给你一个非负整数 x ,计算并返回 x算术平方根

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

**注意:**不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5

示例 1:

输入:x = 4 输出:2 

示例 2:

输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 

提示:

  • 0 <= x <= 231 - 1
题目分析

我们这个题目其实也是可以转换一下,我们这里实际求的是小于等于根号x的最大值,于是这里我们也是可以复用我们第二题的代码的,当然了,这里也是可以复用第三题的代码二的。

实现思路

其实这里就是条件改变了一下,基本的思路和之前还是一模一样的,这里就不再赘述了。

实现代码
代码一
classSolution{public:intmySqrt(int x){int left=1,right=x;if(x<1)return0;// 边界情况while(left<right){longlong mid=left+(right-left+1)/2;if(mid*mid<=x) left=mid;else right=mid-1;}return left;}};
代码二
classSolution{public:intmySqrt(int x){int left=1,right=x;if(x<1)return0;// 边界情况int index =-1;while(left <= right){longlong mid=left+(right-left+1)/2;if(mid*mid<=x){ index = mid; left = mid +1;}else right=mid-1;}return index;}};

Read more

Microsoft Edge WebView2 Runtime(运行库)快速部署 + 调试指南(精简实用、适配开发 + 用户双场景)

Microsoft Edge WebView2 Runtime(运行库)快速部署 + 调试指南(精简实用、适配开发 + 用户双场景)

WebView2运行库 v143.0.3650.139 x64 精简安装(下载) 一、WebView2 Runtime 快速安装部署(用户 / 开发通用,必做) ✅ 1. 系统预装情况 ▸ Windows 11 系统 默认自带 常青版 WebView2 运行库,无需手动安装;▸ Windows 10/7/8.1 需手动安装,缺失则调用 WebView2 控件的软件会弹窗报错「缺少 WebView2 运行环境」。 ✅ 2. 两种官方安装方式(推荐) 方式 1:常青版(Evergreen Runtime)- 首选 ▸ 特点:体积小(引导包仅

By Ne0inhk
地理空间大揭秘:身份证首位数字的隐藏含义-使用WebGIS进行传统6大区域展示

地理空间大揭秘:身份证首位数字的隐藏含义-使用WebGIS进行传统6大区域展示

目录 前言 一、关于身份证的空间信息 1、身份证与省份信息 2、首位数字与区域 二、数字与空间展示可视化 1、地域及图例的前端定义 2、省份与区域信息展示 三、成果展示 1、华北地区 2、东北地区 3、华东地区  4、中南地区 5、西南地区 6、西北地区  四、总结 前言         在我们日常生活中,身份证号码是每个人独一无二的身份标识,它承载着丰富的信息,其中第一位数字更是蕴含着与地理空间紧密相关的秘密。这一位数字并非随意排列,而是与我国广袤的国土划分有着深刻的联系。通过 WebGIS(Web 地理信息系统)技术,我们能够以一种直观、生动的方式,将身份证首位数字所代表的地理区域进行可视化展示,从而揭开传统 6 大区域的神秘面纱。       中国地域辽阔,地理环境复杂多样。

By Ne0inhk
个性化图书推荐系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

个性化图书推荐系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

摘要 随着数字化阅读的普及,个性化图书推荐系统在提升用户体验和满足读者需求方面发挥了重要作用。传统的图书推荐方式往往基于简单的分类或热门榜单,难以满足读者多样化的兴趣偏好。现代推荐系统通过分析用户行为数据、阅读历史和偏好,能够提供更加精准的个性化推荐。本研究旨在开发一个基于SpringBoot后端、Vue前端和MySQL数据库的个性化图书推荐系统,该系统能够通过算法分析用户行为,动态调整推荐内容,从而提升用户的阅读体验和满意度。关键词:个性化推荐、数字化阅读、用户行为分析、动态调整、阅读体验。 本研究采用SpringBoot作为后端框架,结合Vue.js前端技术,构建了一个高效、可扩展的个性化图书推荐系统。系统通过MySQL数据库存储用户数据、图书信息和推荐记录,并利用协同过滤算法和内容-based算法实现精准推荐。功能模块包括用户注册与登录、图书浏览与搜索、推荐列表生成、用户反馈收集等。系统支持管理员对图书信息进行管理,同时提供用户个人中心,方便查看阅读历史和推荐记录。后端采用RESTful API设计,前端通过Axios实现数据交互,确保系统的高效运行和良好的用户体验。关键词:

By Ne0inhk
Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择

Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择

🧑 博主简介:ZEEKLOG博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”)总架构师,16年工作经验,精通Java编程,高并发设计,分布式系统架构设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。 🤝商务合作:请搜索或扫码关注微信公众号 “ 心海云图 ” Springboot 4.0十字路口:虚拟线程时代,WebFlux与WebMVC的终极选择 当虚拟线程以革命性的姿态降临Java世界,一场关于并发编程范式的静默变革正在发生。Spring开发者站在了选择的十字路口。 2023年,Java 21将虚拟线程从预览特性转为正式功能,这一变化看似只是JVM内部的优化,实则撼动了整个

By Ne0inhk