【优选算法】——二分查找!

【优选算法】——二分查找!

目录

1、二分查找

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

3、搜索插入位置

4、x的平方根

5、山脉数组的封顶索引

6、寻找峰值 

7、寻找旋转排序数组中的最小值

8、点名

9、完结散花


1、二分查找

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


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

题解:这个题目我们只要用到朴素版的二分查找算法即可!

解题代码:

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

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

给你一个按照非递减顺序排列的整数数组 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]

解题代码:

class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if(nums.empty()) return {-1,-1};//处理边界情况 vector<int> ret; int left=0,right=nums.size()-1; //求左端点 while(left<right)//判断条件一定是<,如果<=则会进入死循环 { int mid=left+(right-left)/2;//不能加1,否则死循环 if(nums[mid]<target) left=mid+1; else right=mid; } ret.push_back(nums[right]==target?right:-1); //求右端点 left=0,right=nums.size()-1; while(left<right) { int mid=left+(right-left+1)/2;//一定要加1,否则死循环 if(nums[mid]>target) right=mid-1; else left=mid; } ret.push_back(nums[left]==target?left:-1); return ret; } };

3、搜索插入位置

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

请必须使用时间复杂度为 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

解题代码: 

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

4、x的平方根

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

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

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



示例 1:输入:x = 4 输出:2

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

解题代码:

class Solution { public: int mySqrt(int x) { //求右端点 long long left=0,right=x; while(left<right) { long long mid=left+(right-left+1)/2; if(mid*mid<=x) left=mid; else right=mid-1; } return left; } };

5、山脉数组的封顶索引

给定一个长度为 n 的整数 山脉 数组 arr ,其中的值递增到一个 峰值元素 然后递减。

返回峰值元素的下标。

你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。



示例 1:输入:arr = [0,1,0] 输出:1

示例 2:输入:arr = [0,2,1,0] 输出:1

示例 3:输入:arr = [0,10,5,2] 输出:1

class Solution { public: int peakIndexInMountainArray(vector<int>& arr) { int left=0,right=arr.size()-1; //求最右端点 while(left<right) { int mid=left+(right-left+1)/2; if(arr[mid]<arr[mid-1]) right=mid-1; else left=mid; } return left; } };

6、寻找峰值 

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

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



示例 1:输入:nums = [1,2,3,1]输出:2 解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:输入:nums = [1,2,1,3,5,6,4] 输出:1 或 5 解释:你的函数可以返回索引 1,其峰值元素为 2;   或者返回索引 5, 其峰值元素为 6。

class Solution { public: int findPeakElement(vector<int>& nums) { int left=0,right=nums.size()-1; //求最右端点 while(left<right) { int mid=left+(right-left+1)/2; if(nums[mid]<nums[mid-1]) right=mid-1; else left=mid; } return left; } };

7、寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

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



示例 1:输入:nums = [3,4,5,1,2] 输出:1 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:输入:nums = [4,5,6,7,0,1,2] 输出:0 解释:原数组为 [0,1,2,4,5,6,7] ,旋转 3 次得到输入数组。

示例 3:输入:nums = [11,13,15,17] 输出:11 解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

class Solution { public: int findMin(vector<int>& nums) { int n=nums.size()-1; //处理特殊情况,即未发生旋转,或旋转到原数组 if(nums[0]<nums[n]) return nums[0]; int left=0,right=n; //求最左端点 while(left<right) { int mid=left+(right-left)/2; if(nums[mid]<nums[0]) right=mid; else left=mid+1; } return nums[left]; } };

8、点名

某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席,请返回他的学号。



示例 1:输入: records = [0,1,2,3,5] 输出: 4

示例 2:输入: records = [0, 1, 2, 3, 4, 5, 6, 8] 输出: 7

class Solution { public: int takeAttendance(vector<int>& r) { int end=r.size()-1; //特殊情况处理 if(r[end]==end) return end+1; int left=0,right=end; //求最左端点的二分查找 while(left<right) { int mid=left+(right-left)/2; if(r[mid]>mid) right=mid; else left=mid+1; } return right; } };

其他解法: 

>hash映射

class Solution { public: int takeAttendance(vector<int>& r) { int n=r.size()+1; int hash[10000]={0}; for(auto e:r) hash[e]++; for(int i=0;i<n;i++) { if(hash[i]==0) return i; } return n; } };

>位运算

class Solution { public: int takeAttendance(vector<int>& r) { int ret=0; for(int i=0;i<r.size();i++) { ret^=r[i]; ret^=i; } return ret^=r.size(); } };

>暴力查找

class Solution { public: int takeAttendance(vector<int>& r) { int ret=0; for(int i=0;i<r.size();i++) { if(r[i]!=i) return i; } return r.size(); } };

>数学(高斯求和公式)

class Solution { public: int takeAttendance(vector<int>& r) { int ret=0,sum1=0,sum2=0; for(int i=0;i<r.size();i++) { sum1+=r[i]; sum2+=i; } return sum2-sum1+r.size(); } };

9、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​

​​

Read more

【笔试】算法的暴力美学——牛客 NC221681:dd爱框框

【笔试】算法的暴力美学——牛客 NC221681:dd爱框框

一、题目描述 二、算法原理 思路:滑动窗口 1)定义两个指针,一开始都为0,cur 从左开始遍历,定义一个 sum 来表示 prev 到 cur 的之间的值的总和,当 sum >= x 时,我们要根据题目条件来保存 prev 和 cur 的值; 2)当 sum >= x 时,我们记录完 prev 和 cur 的值的之后,sum -= arr[ prev ],prev++ ,往后走,只要满足条件 sum >= x 我们就要记录

By Ne0inhk
Leetcode 202题 快乐数:数字世界中的奇妙旅程

Leetcode 202题 快乐数:数字世界中的奇妙旅程

Leetcode 202题 快乐数:数字世界中的奇妙旅程 * 视频地址 * 解题思路:从数字到链表的思维转换 * 链表思维的巧妙应用 * 快慢指针:龟兔赛跑的智慧 * 算法实现:C++代码解析 * 关键函数:数字变换 * 快乐数判断主逻辑 * 数学深度:数字会无限增大吗? * 快乐数的性质与统计 * 复杂度分析与优化 * 扩展思考 视频地址 因为想更好的为大佬服务,制作了同步视频,这是Bilibili的视频地址 在数学的奇妙花园里,有一种特殊的数字被赋予了"快乐"的称号。快乐数(Happy Number)就像一位在数字迷宫中寻找出口的旅人,它遵循着特定的变换规则,一步步走向最终的归宿——1。 快乐数的定义:对于一个正整数,如果将其各位数字的平方和不断进行替换,最终能够得到1,那么这个数就被称为快乐数。反之,如果陷入一个不包含1的循环,那么这个数就是不快乐的。 让我们以19为例,展开这段数字的奇妙旅程: 19 → 1²

By Ne0inhk
排序算法指南:归并排序(非递归)

排序算法指南:归并排序(非递归)

前言:              非递归实现归并排序,通常被称为 “自底向上”(Bottom-Up) 的归并排序,与递归版本(先将数组对半拆分直到只剩一个元素,再通过递归栈回溯合并)不同,非递归版本直接从最小的子数组(长度为1)开始,两两合并,然后长度翻倍(2, 4, 8 ...),直到合并完整个数组。                                                                 一、归并排序非递归的核心思路          递归算法转换为非递归实现主要有两种常见方法:          1.使用栈结构模拟递归过程          2.将递归逻辑改写为循环结构          1.1 栈模拟失效          如果仅通过栈结构模拟递归过程,我们只能够做到拆分数组,而不能做到合并数组。          假设我们要排序数组 arr = [8, 4, 5, 7],下标是 0 到 3。          初始状态:栈中有任务 [0, 3]。                   第一步:弹

By Ne0inhk
Python APP反爬实战:Frida+Charles抓包,破解签名校验

Python APP反爬实战:Frida+Charles抓包,破解签名校验

做爬虫做到APP层面,你会发现网页反爬的那套思路完全失效:用Charles抓包能看到请求参数,但sign/appSign这类签名参数始终是乱码;手动拼接参数模拟请求,服务端直接返回“签名验证失败”;甚至换了代理、改了设备信息,还是过不了服务端的校验——这就是APP反爬的核心壁垒:签名校验。 我经手过电商APP价格爬取、短视频APP数据采集、物流APP轨迹抓取等数十个APP反爬项目,从最初的“抓包改参数被秒拒”,到后来用Frida Hook脱壳获取签名密钥,再到Python还原签名算法实现稳定爬取,踩过的坑能帮新手少走一年弯路。这篇文章全程聚焦“实战”:从APP签名校验的底层逻辑,到Charles抓包定位参数,再到Frida Hook破解签名算法,最后用Python实现完整爬取,每个步骤都附可直接复制的生产级代码,新手也能跟着搞定99%的APP签名反爬。 一、先搞懂:APP签名校验的核心逻辑(为什么普通抓包没用) 新手先别着急装工具,搞懂签名校验的原理,才能精准破解——这是APP反爬的“命门”,也是服务端验证请求合法性的核心手段。 1.1 APP反爬 vs 网页反爬:核心差异

By Ne0inhk