
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法的世界里,有一类算法看似简洁易懂,却能在海量数据中实现效率的飞跃,二分查找便是其中的典范.它作为优选算法家族中不可或缺的一员,以"分而治之"的核心思想,将线性查找的低效困境彻底打破,成为面试高频、工程常用的核心算法之一.二分查找的价值,远不止于"查找目标值"这一基础功能.它的核心魅力在于"高效收缩范围"的思想,将原本O(n)的线性时间复杂度优化至O(log n),这种效率的提升在数据量庞大时尤为显著------当数据规模达到100万时,线性查找最坏需遍历100万次,而二分查找仅需20次左右;当数据突破10亿,二分查找也只需30次就能锁定范围,这种量级的差距,正是优选算法的核心意义所在.更重要的是,二分查找的思想并非局限于单纯的查找场景,它还能延伸到最值求解、边界查找、旋转数组查找等复杂问题中,成为解决各类"二段性"问题的通用思路.本文将跳出"死记模板"的误区,结合实战中的亲身感悟,拆解二分查找的思想原理------从"二段性"这一核心本质出发,解读其分治思想的底层逻辑,剖析实战中常见的边界陷阱与避坑技巧,让读者不仅能看懂二分查找的代码,更能理解每一行逻辑背后的思考,真正将"分而治之"的思想内化为解决问题的能力.毕竟,算法的学习从来不是背诵代码,而是领悟其背后的思维方式,二分查找的实战之路,既是对细节的打磨,更是对高效解题思维的锤炼.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
- 1.二分查找算法背景介绍
- 2.二分查找(OJ题)
- 3.在排序数组中查找元素的第⼀个和最后⼀个位置(OJ题)
- 4.x的平方根(OJ题)
- 5.搜索插入位置(OJ题)
- 6.山脉数组的峰顶索引(OJ题)
- 7.寻找峰值(OJ题)
- 8.寻找旋转排序数组中的最小值(OJ题)
- 9.点名(OJ题)
1.二分查找算法背景介绍
1️⃣算法背景
二分查找(Binary Search),也叫折半查找 ,是一种针对有序数据集 的高效查找算法,属于减治算法 (将问题规模每次缩小一半)的经典应用.
在二分查找出现前,最基础的查找方式是顺序查找(线性查找):逐个遍历数据元素,直到找到目标值.
- 顺序查找时间复杂度为 O(n),数据量较小时尚可使用;
- 当数据量达到百万、千万级别时,顺序查找效率极低,无法满足快速检索需求.
- 二分查找正是为了解决大规模有序数据的高效查找问题而生,通过不断缩小查找范围,大幅降低查找次数.
2️⃣核心适用前提
二分查找并非所有场景都能用 ,必须满足3个关键条件:
1.数据序列有序 (升序或降序排列);
-
数据结构支持随机访问 (如数组,链表因无法快速定位中间元素,不适合二分查找);
-
数据为静态数据(无频繁插入/删除,否则会破坏有序性).
3️⃣核心原理
二分查找的核心思想:每次取查找区间的中间元素与目标值比较,将查找范围直接缩小一半,重复此过程直到找到目标或确认目标不存在 .
简单理解:就像查字典,不会从头翻到尾,而是先翻中间页,根据页码大小判断目标字在左半还是右半,再继续折半查找.
2.二分查找(OJ题)

算法流程
①定义 left,right 指针,分别指向数组的左右区间.
②找到待查找区间的中间点 mid,找到之后分三种情况讨论:
(1)arr[mid] == target 说明正好找到,返回 mid 的值;
(2)arr[mid] > target 说明 [mid, right] 这段区间都是大于 target 的,因此舍去右边区间,在左边 [left, mid - 1] 的区间继续查找,即让 right = mid - 1,然后重复2过程;
(3)arr[mid] < target 说明 [left, mid] 这段区间的值都是小于 target 的,因此舍去左边区间,在右边 [mid + 1, right] 区间继续查找,即让 left = mid + 1,然后重复 2 过程;
③当 left 与 right 错开时,说明整个区间都没有这个数,返回 -1.

核心代码
cpp
class Solution
{
public:
int search(vector<int>&nums, int target)
{
//初始化 left 与 right 指针
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)
right = mid - 1;
else
left = mid + 1;
}
//如果程序⾛到这⾥,说明没有找到⽬标值,返回 -1
return -1;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
int search(vector<int>&nums, int target)
{
//初始化left与right指针
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)
right = mid - 1;
else
left = mid + 1;
}
//如果程序⾛到这⾥,说明没有找到⽬标值,返回 -1
return -1;
}
};
int main()
{
Solution sol;
vector<int> nums1 = {1, 3, 5, 7, 9};
int target1 = 5;
cout << "测试用例1:数组 [1,3,5,7,9],查找 5" << endl;
cout << "查找结果下标:" << sol.search(nums1, target1) << "\n\n";
vector<int> nums2 = {2, 4, 6, 8, 10};
int target2 = 2;
cout << "测试用例2:数组 [2,4,6,8,10],查找 2" << endl;
cout << "查找结果下标:" << sol.search(nums2, target2) << "\n\n";
vector<int> nums3 = {0, 1, 2, 3, 4};
int target3 = 4;
cout << "测试用例3:数组 [0,1,2,3,4],查找 4" << endl;
cout << "查找结果下标:" << sol.search(nums3, target3) << "\n\n";
vector<int> nums4 = {10, 20, 30};
int target4 = 25;
cout << "测试用例4:数组 [10,20,30],查找 25" << endl;
cout << "查找结果下标:" << sol.search(nums4, target4) << "\n\n";
vector<int> nums5 = {8};
int target5 = 8;
cout << "测试用例5:数组 [8],查找 8" << endl;
cout << "查找结果下标:" << sol.search(nums5, target5) << "\n\n";
vector<int> nums6 = {8};
int target6 = 6;
cout << "测试用例6:数组 [8],查找 6" << endl;
cout << "查找结果下标:" << sol.search(nums6, target6) << endl;
return 0;
}

3.在排序数组中查找元素的第⼀个和最后⼀个位置(OJ题)

算法思路
用的还是二分思想,就是根据数据的性质,在某种判断条件下将区间一分为二,然后舍去其中一个区间,然后再另一个区间内查找;
方便叙述,用 x 表示该元素,resLeft 表示左边界,resRight 表示右边界.
寻找左边界思路:
-
寻找左边界:
- 我们注意到以左边界划分的两个区间的特点:
- 左边区间
[left, resLeft - 1]都是小于x的; - 右边区间(包括左边界)
[resLeft, right]都是大于等于x的;
- 左边区间
- 因此,关于
mid的落点,我们可以分为下面两种情况:- 当我们的
mid落在[left, resLeft - 1]区间的时候,也就是arr[mid] < target.说明[left, mid]都是可以舍去的,此时更新left到mid + 1的位置,继续在[mid + 1, right]上寻找左边界; - 当
mid落在[resLeft, right]的区间的时候,也就是arr[mid] >= target.说明[mid + 1, right](因为mid可能是最终结果,不能舍去)是可以舍去的,此时更新right到mid的位置,继续在[left, mid]上寻找左边界;
- 当我们的
- 我们注意到以左边界划分的两个区间的特点:
-
由此,就可以通过二分,来快速寻找左边界;
注意 : 这里找中间元素需要向下取整.
因为后续移动左右指针的时候:
- 左指针:
left = mid + 1,是会向后移动的,因此区间是会缩小的; - 右指针:
right = mid,可能会原地踏步(比如: 如果向上取整的话,如果剩下1,2 两个元素,left == 1,right == 2,mid == 2.更新区间之后,left,right,mid的值没有改变,就会陷入死循环).
因此一定要注意,当right = mid的时候,要向下取整.
寻找右边界思路:
-
寻右左边界:
- 用
resRight表示右边界; - 我们注意到右边界的特点:
- 左边区间(包括右边界)
[left, resRight]都是小于等于x的; - 右边区间
[resRight+ 1, right]都是大于x的;
- 左边区间(包括右边界)
- 因此,关于
mid的落点,我们可以分为下面两种情况:- 当我们的
mid落在[left, resRight]区间的时候,说明[left, mid - 1](mid不可以舍去,因为有可能是最终结果)都是可以舍去的,此时更新left到mid的位置 - 当
mid落在[resRight+ 1, right]的区间的时候,说明[mid, right]内的元素是可以舍去的,此时更新right到mid - 1的位置;
- 当我们的
- 用
-
由此,就可以通过二分,来快速寻找右边界;
注意 : 这里找中间元素需要向上取整。
因为后续移动左右指针的时候:
- 左指针:
left = mid,可能会原地踏步(比如: 如果向下取整的话,如果剩下 1,2 两个元素,left == 1,right == 2,mid == 1.更新区间之后,left,right,mid的值没有改变,就会陷入死循环). - 右指针:
right = mid - 1,是会向前移动的,因此区间是会缩小的;
因此一定要注意,当right = mid的时候,要向下取整.
二分查找算法总结:
请大家一定不要觉得背下模板就能解决所有二分问题.二分问题最重要的就是要分析题意,然后确定要搜索的区间,根据分析问题来写出二分查找算法的代码.要分析题意,确定搜索区间,不要死记模板,不要看左闭右开什么乱七八糟的题解.
模板记忆技巧:
1.关于什么时候用三段式,还是二段式中的某一个,一定不要强行去用,而是通过具体的问题分析情况,根据查找区间的变化确定指针的转移过程,从而选择一个模板.
2.当选择两段式的模板时:
在求mid的时候,只有 right - 1 的情况下,才会向上取整(也就是 +1 取中间数)

核心代码
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//处理边界情况
if (nums.size() == 0)
return {-1, -1};
int begin = 0;
//1. ⼆分左端点
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
//判断是否有结果
if (nums[left] != target)
return {-1, -1};
else
begin = left; //标记⼀下左端点
//2. ⼆分右端点
left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] <= target)
left = mid;
else
right = mid - 1;
}
return {begin, right};
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
//处理边界情况
if (nums.size() == 0)
return {-1, -1};
int begin = 0;
//1.⼆分左端点
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
//判断是否有结果
if (nums[left] != target)
return {-1, -1};
else
begin = left; //标记⼀下左端点
//2.⼆分右端点
left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (nums[mid] <= target)
left = mid;
else
right = mid - 1;
}
return {begin, right};
}
};
int main() {
Solution sol;
vector<int> nums1 = {};
int target1 = 0;
vector<int> res1 = sol.searchRange(nums1, target1);
cout << "测试用例1:空数组,查找 0" << endl;
cout << "结果:[" << res1[0] << "," << res1[1] << "]" << "\n\n";
vector<int> nums2 = {5};
int target2 = 5;
vector<int> res2 = sol.searchRange(nums2, target2);
cout << "测试用例2:数组 [5],查找 5" << endl;
cout << "结果:[" << res2[0] << "," << res2[1] << "]" << "\n\n";
vector<int> nums3 = {5};
int target3 = 3;
vector<int> res3 = sol.searchRange(nums3, target3);
cout << "测试用例3:数组 [5],查找 3" << endl;
cout << "结果:[" << res3[0] << "," << res3[1] << "]" << "\n\n";
vector<int> nums4 = {5,7,7,8,8,10};
int target4 = 8;
vector<int> res4 = sol.searchRange(nums4, target4);
cout << "测试用例4:数组 [5,7,7,8,8,10],查找 8" << endl;
cout << "结果:[" << res4[0] << "," << res4[1] << "]" << "\n\n";
vector<int> nums5 = {2,2,2,3,4};
int target5 = 2;
vector<int> res5 = sol.searchRange(nums5, target5);
cout << "测试用例5:数组 [2,2,2,3,4],查找 2" << endl;
cout << "结果:[" << res5[0] << "," << res5[1] << "]" << "\n\n";
vector<int> nums6 = {1,3,5,7,7,7};
int target6 = 7;
vector<int> res6 = sol.searchRange(nums6, target6);
cout << "测试用例6:数组 [1,3,5,7,7,7],查找 7" << endl;
cout << "结果:[" << res6[0] << "," << res6[1] << "]" << "\n\n";
vector<int> nums7 = {5,7,7,8,8,10};
int target7 = 6;
vector<int> res7 = sol.searchRange(nums7, target7);
cout << "测试用例7:数组 [5,7,7,8,8,10],查找 6" << endl;
cout << "结果:[" << res7[0] << "," << res7[1] << "]" << "\n\n";
vector<int> nums8 = {1,2,3,4,5};
int target8 = 3;
vector<int> res8 = sol.searchRange(nums8, target8);
cout << "测试用例8:数组 [1,2,3,4,5],查找 3" << endl;
cout << "结果:[" << res8[0] << "," << res8[1] << "]" << endl;
return 0;
}

4.x的平方根(OJ题)

算法思路:解法一(暴力查找)
依次枚举 [0, x] 之间的所有数 i:
(这里没有必要研究是否枚举到 x / 2 还是 x / 2 + 1.因为我们找到结果之后直接就返回了,往后的情况就不会再判断.反而研究枚举区间,既耽误时间,又可能出错)
- 如果
i * i == x,直接返回x; - 如果
i * i > x,说明之前的一个数是结果,返回i - 1.
由于 i * i 可能超过 int 的最大值,因此使用 long long 类型.
核心代码
cpp
class Solution {
public:
int mySqrt(int x) {
//由于两个较⼤的数相乘可能会超过 int 最⼤范围
//因此⽤ long long
long long i = 0;
for (i = 0; i <= x; i++) {
//如果两个数相乘正好等于 x,直接返回 i
if (i * i == x)
return i;
//如果第⼀次出现两个数相乘⼤于 x,说明结果是前⼀个数
if (i * i > x)
return i - 1;
}
//为了处理oj题需要控制所有路径都有返回值
return -1;
}
};
算法思路:解法二(二分查找算法)
设 x 的平方根的最终结果为 index:
分析 index 左右两次数据的特点:
[0, index]之间的元素,平方之后都是小于等于x的;[index + 1, x]之间的元素,平方之后都是大于x的.
因此可以使用二分查找算法.
核心代码
cpp
class Solution {
public:
int mySqrt(int x) {
if (x < 1)
return 0; //处理边界情况
int left = 1, 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;
}
};
完整测试代码
cpp
#include <iostream>
using namespace std;
class Solution {
public:
int mySqrt(int x) {
if (x < 1)
return 0; //处理边界情况
int left = 1, right = x;
while (left < right) {
//mid 用long long防止平方后溢出
long long mid = left + (right - left + 1)/2; //向上取整,防死循环
if (mid * mid <= x)
left = mid;
else
right = mid - 1;
}
return left;
}
};
int main() {
Solution sol;
cout << "测试用例1:x = 0,平方根 = " << sol.mySqrt(0) << endl;
cout << "测试用例2:x = 1,平方根 = " << sol.mySqrt(1) << endl;
cout << "测试用例3:x = 2,平方根 = " << sol.mySqrt(2) << endl;
cout << "测试用例4:x = 4,平方根 = " << sol.mySqrt(4) << endl;
cout << "测试用例5:x = 8,平方根 = " << sol.mySqrt(8) << endl;
cout << "测试用例6:x = 9,平方根 = " << sol.mySqrt(9) << endl;
cout << "测试用例7:x = 16,平方根 = " << sol.mySqrt(16) << endl;
cout << "测试用例8:x = 2147483647,平方根 = " << sol.mySqrt(2147483647) << endl;
return 0;
}

5.搜索插入位置(OJ题)

算法思路:解法(二分查找算法)
①分析插入位置左右两侧区间上元素的特点:
设插入位置的坐标为 index,根据插入位置的特点可以知道:
[left, index - 1]内的所有元素均是小于target的;[index, right]内的所有元素均是大于等于target的.
②设 left 为本轮查询的左边界,right 为本轮查询的右边界.根据 mid 位置元素的信息,分析下一轮查询的区间:
- 当
nums[mid] >= target时,说明mid落在了[index, right]区间上,mid左边包括mid本身,可能是最终结果,所以我们接下来查找的区间在[left, mid]上.因此,更新right到mid位置,继续查找. - 当
nums[mid] < target时,说明mid落在了[left, index - 1]区间上,mid右边但不包括mid本身,可能是最终结果,所以我们接下来查找的区间在[mid + 1, right]上.因此,更新left到mid + 1的位置,继续查找.
③直到我们的查找区间的长度变为1,也就是 left == right 的时候,left 或者 right 所在的位置就是我们要找的结果.
核心代码
cpp
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)
left = mid + 1;
else
right = mid;
}
if (nums[left] < target)
return right + 1;
return right;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if(nums.empty()) return 0;
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target)
left = mid + 1;
else
right = mid;
}
if (nums[left] < target)
return right + 1;
return right;
}
};
int main() {
Solution sol;
vector<int> nums1;
cout << "1. 空数组,插入 5 → 位置:" << sol.searchInsert(nums1, 5) << endl;
vector<int> nums2 = {1,3,5,6};
cout << "2. [1,3,5,6] 查找 5 → 位置:" << sol.searchInsert(nums2, 5) << endl;
cout << "3. [1,3,5,6] 插入 2 → 位置:" << sol.searchInsert(nums2, 2) << endl;
cout << "4. [1,3,5,6] 插入 7 → 位置:" << sol.searchInsert(nums2, 7) << endl;
cout << "5. [1,3,5,6] 插入 0 → 位置:" << sol.searchInsert(nums2, 0) << endl;
vector<int> nums3 = {8};
cout << "6. [8] 查找 8 → 位置:" << sol.searchInsert(nums3, 8) << endl;
cout << "7. [8] 插入 10 → 位置:" << sol.searchInsert(nums3, 10) << endl;
return 0;
}

6.山脉数组的峰顶索引(OJ题)

算法思路:解法一(暴力查找)
峰顶的特点:比两侧的元素都要大.
因此,我们可以遍历数组内的每一个元素,找到某一个元素比两边的元素大即可.
核心代码
cpp
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int n = arr.size();
//遍历数组内每⼀个元素,直到找到峰顶
for (int i = 1; i < n - 1; i++)
//峰顶满⾜的条件
if (arr[i] > arr[i - 1] && arr[i] > arr[i + 1])
return i;
//为了处理 oj 需要控制所有路径都有返回值
return -1;
}
};
算法思路:解法二(二分查找)
1.分析峰顶位置的数据特点,以及山峰两旁的数据的特点:
- 峰顶数据特点:
arr[i] > arr[i - 1] && arr[i] > arr[i + 1]; - 峰顶左边的数据特点:
arr[i] > arr[i - 1] && arr[i] < arr[i + 1],也就是呈现上升趋势; - 峰顶右边数据的特点:
arr[i] < arr[i - 1] && arr[i] > arr[i + 1],也就是呈现下降趋势.
2.因此,根据 mid 位置的信息,我们可以分为下面三种情况:
- 如果
mid位置呈现上升趋势,说明我们接下来要在[mid + 1, right]区间继续搜索; - 如果
mid位置呈现下降趋势,说明我们接下来要在[left, mid - 1]区间搜索; - 如果
mid位置就是山峰,直接返回结果.
核心代码
cpp
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int left = 1, right = arr.size() - 2;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (arr[mid] > arr[mid - 1])
left = mid;
else
right = mid - 1;
}
return left;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr) {
int left = 1, right = arr.size() - 2;
while (left < right) {
int mid = left + (right - left + 1) / 2;
if (arr[mid] > arr[mid - 1])
left = mid;
else
right = mid - 1;
}
return left;
}
};
int main() {
Solution sol;
vector<int> arr1 = {0,1,0};
cout << "测试用例1:数组 [0,1,0]" << endl;
cout << "峰值索引:" << sol.peakIndexInMountainArray(arr1) << "\n\n";
vector<int> arr2 = {0,2,1,0};
cout << "测试用例2:数组 [0,2,1,0]" << endl;
cout << "峰值索引:" << sol.peakIndexInMountainArray(arr2) << "\n\n";
vector<int> arr3 = {0,10,5,2};
cout << "测试用例3:数组 [0,10,5,2]" << endl;
cout << "峰值索引:" << sol.peakIndexInMountainArray(arr3) << "\n\n";
vector<int> arr4 = {1,3,5,7,6,4,2};
cout << "测试用例4:数组 [1,3,5,7,6,4,2]" << endl;
cout << "峰值索引:" << sol.peakIndexInMountainArray(arr4) << "\n\n";
vector<int> arr5 = {3,4,5,1};
cout << "测试用例5:数组 [3,4,5,1]" << endl;
cout << "峰值索引:" << sol.peakIndexInMountainArray(arr5) << endl;
return 0;
}

7.寻找峰值(OJ题)

算法思路:解法(二分查找算法)
寻找二段性:
任取一个点 i,与下一个点 i + 1,会有如下两种情况:
arr[i] > arr[i + 1]:此时左侧区域一定会存在山峰(因为最左侧是负无穷),那么我们可以去左侧去寻找结果;arr[i] < arr[i + 1]:此时右侧区域一定会存在山峰(因为最右侧是负无穷),那么我们可以去右侧去寻找结果.
当我们找到二段性的时候,就可以尝试用二分查找算法来解决问题.
核心代码
cpp
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right)
{
// 关键:向上取整,避免区间只剩2个元素时死循环
int mid = left + (right - left ) / 2;
if (nums[mid] > nums[mid + 1])
{
// 下降趋势:峰值在[left, mid],收缩右边界
right = mid;
} else
{
// 上升趋势:峰值在[mid+1, right],收缩左边界
left = mid + 1;
}
}
// 循环结束left==right,即为峰值索引
return left;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right)
{
// 关键:向上取整,避免区间只剩2个元素时死循环
int mid = left + (right - left ) / 2;
if (nums[mid] > nums[mid + 1])
{
// 下降趋势:峰值在[left, mid],收缩右边界
right = mid;
} else
{
// 上升趋势:峰值在[mid+1, right],收缩左边界
left = mid + 1;
}
}
// 循环结束left==right,即为峰值索引
return left;
}
};
int main() {
Solution sol;
vector<int> nums1 = {1,2,3,1};
cout << "测试用例1:数组 [1,2,3,1]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums1) << "\n\n";
vector<int> nums2 = {1,2,1,3,5,6,4};
cout << "测试用例2:数组 [1,2,1,3,5,6,4]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums2) << "\n\n";
vector<int> nums3 = {5};
cout << "测试用例3:数组 [5]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums3) << "\n\n";
vector<int> nums4 = {1,2,3,4};
cout << "测试用例4:数组 [1,2,3,4]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums4) << "\n\n";
vector<int> nums5 = {4,3,2,1};
cout << "测试用例5:数组 [4,3,2,1]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums5) << "\n\n";
vector<int> nums6 = {0,1};
cout << "测试用例6:数组 [0,1]" << endl;
cout << "峰值索引:" << sol.findPeakElement(nums6) << endl;
return 0;
}

8.寻找旋转排序数组中的最小值(OJ题)

关于暴⼒查找,只需遍历⼀遍数组,这⾥不再赘述.
算法思路:解法(⼆分查找)
题⽬中的数组规则如下图所示:

其中 C 点就是我们要求的点。
二分的本质:找到一个判断标准,使得查找区间能够一分为二.
通过图像我们可以发现,[A,B] 区间内的点都是严格大于 D 点的值的,C 点的值是严格小于 D 点的值的.但是当 [C,D] 区间只有一个元素的时候,C 点的值是可能等于 D 点的值的.
因此,初始化左右两个指针 left,right:
然后根据 mid 的落点,我们可以这样划分下一次查询的区间:
- 当
mid在[A,B]区间的时候,也就是mid位置的值严格大于 D 点的值,下一次查询区间在[mid + 1,right]上; - 当
mid在[C,D]区间的时候,也就是mid位置的值严格小于等于 D 点的值,下次查询区间在[left,mid]上.
当区间长度变成1的时候,就是我们要找的结果.
核心代码
cpp
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
int x = nums[right]; //标记⼀下最后⼀个位置的值
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > x)
left = mid + 1;
else
right = mid;
}
return nums[left];
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int findMin(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
int x = nums[right]; //标记⼀下最后⼀个位置的值
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > x)
left = mid + 1;
else
right = mid;
}
return nums[left];
}
};
int main() {
Solution sol;
vector<int> nums1 = {3,4,5,1,2};
cout << "测试用例1:[3,4,5,1,2] → 最小值:" << sol.findMin(nums1) << "\n\n";
vector<int> nums2 = {4,5,6,7,0,1,2};
cout << "测试用例2:[4,5,6,7,0,1,2] → 最小值:" << sol.findMin(nums2) << "\n\n";
vector<int> nums3 = {1,2,3,4,5};
cout << "测试用例3:[1,2,3,4,5] → 最小值:" << sol.findMin(nums3) << "\n\n";
vector<int> nums4 = {1};
cout << "测试用例4:[1] → 最小值:" << sol.findMin(nums4) << "\n\n";
vector<int> nums5 = {2,1};
cout << "测试用例5:[2,1] → 最小值:" << sol.findMin(nums5) << "\n\n";
vector<int> nums6 = {1,2};
cout << "测试用例6:[1,2] → 最小值:" << sol.findMin(nums6) << endl;
return 0;
}

9.点名(OJ题)

算法思路:解法(二分查找算法)
关于这道题中,时间复杂度为 O(N) 的解法有很多种,而且也是比较好想的,这里就不再赘述.
本题只讲解一个最优的二分法,来解决这个问题.
在这个升序的数组中,我们发现:
- 在第一个缺失位置的左边,数组内的元素都是与数组的下标相等的;
- 在第一个缺失位置的右边,数组内的元素与数组下标是不相等的.
因此,我们可以利用这个二段性,来使用二分查找算法.
核心代码
cpp
class Solution {
public:
int takeAttendance(vector<int>& records) {
int left = 0, right = records.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2; // 避免溢出
if (records[mid] == mid) {
// 前半部分无缺失,向右查找
left = mid + 1;
} else {
// 缺失在左半部分(包含mid),向左收缩
right = mid;
}
}
// 边界判断:若最后一个位置仍满足records[left]==left,说明缺失的是最后一个学号(records.size())
return records[left] == left ? records.size() : left;
}
};
完整测试代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
int takeAttendance(vector<int>& records) {
int left = 0, right = records.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2; //避免溢出
if (records[mid] == mid) {
//前半部分无缺失,向右查找
left = mid + 1;
} else {
//缺失在左半部分(包含mid),向左收缩
right = mid;
}
}
//边界判断:若最后一个位置仍满足records[left]==left,说明缺失的是最后一个学号(records.size())
return records[left] == left ? records.size() : left;
}
};
int main() {
Solution sol;
vector<int> rec1 = {0,1,2,3,5};
cout << "测试用例1:[0,1,2,3,5] → 缺席学号:" << sol.takeAttendance(rec1) << endl;
vector<int> rec2 = {0,1,2,3,4,5,6,8};
cout << "测试用例2:[0,1,2,3,4,5,6,8] → 缺席学号:" << sol.takeAttendance(rec2) << endl;
vector<int> rec3 = {0};
cout << "测试用例3:[0] → 缺席学号:" << sol.takeAttendance(rec3) << endl;
vector<int> rec4 = {1};
cout << "测试用例4:[1] → 缺席学号:" << sol.takeAttendance(rec4) << endl;
vector<int> rec5 = {0,1,2};
cout << "测试用例5:[0,1,2] → 缺席学号:" << sol.takeAttendance(rec5) << endl;
vector<int> rec6 = {0,2};
cout << "测试用例6:[0,2] → 缺席学号:" << sol.takeAttendance(rec6) << endl;
return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容:【优选算法】(实战领略前缀和的真谛)
每日心灵鸡汤:不要因为小事闷闷不乐!
烦躁的时候就尽量少说话,也不要做任何决定,安静的待会,你已经长大了,一些难过的情绪,要学会自己消化,不为难自己,别自我折磨、自我消耗.有人握紧你的手就可能会松开,有人给你承诺就有失望,心
情不好就听歌,饿了就吃饭,怕黑就一直开灯睡觉,把期待降低,把依赖变少,努力把生活过成自己想要
的样子.希望你做什么就去做,做自由的风,做纯粹的火,做缥缈的云,希望你不被世俗缠绕,不被谣言淹没,希望你做自己,随心所欲,总之去做炽热的光吧,太阳也好,月亮也罢,做最炽热,做最闪耀最像你的你.
