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

C++ 面试高频考点 力扣 852. 山脉数组的峰顶索引 二分查找 题解 每日一题

文章目录

  • 题目描述
  • 为什么这道题值得弄懂?
  • 为什么可以用二分?
  • 二分查找的核心思路:基于“递增/递减段”划分区间
  • 代码实现
  • 总结:二分查找的关键细节复盘
  • 下一篇题目预告

题目描述

题目链接:
力扣852. 山脉数组的峰顶索引

在这里插入图片描述
在这里插入图片描述

题目描述:
给定一个长度为 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

注意:
3 <= arr.length <= 105
0 <= arr[i] <= 106
题目数据 保证 arr 是一个山脉数组

为什么这道题值得弄懂?

这道题是二分查找的经典**“单调性分段”问题**——山脉数组虽整体不单调,但可根据“峰顶左侧递增、右侧递减”的特性,划分出具有明确规律的两段区间。通过这道题,能进一步掌握“如何根据题目特性定义二分的判断条件”,并强化对“指针移动逻辑”“边界初始化”等细节的理解,为解决更复杂的分段查找问题(如旋转数组查找)打下基础。

为什么可以用二分?

二分查找的核心是利用数组的“二段性”——将数组分为“满足某条件”和“不满足某条件”的两部分,每次舍弃其中一部分,从而将时间复杂度压缩到 O(log n)。

本题中,山脉数组的“二段性”体现在:

  • 对于任意下标 mid,若 arr[mid] < arr[mid + 1]:说明 mid 处于递增段,峰顶一定在 mid 的右侧(包括 mid + 1);
  • arr[mid] > arr[mid + 1]:说明 mid 处于递减段,峰顶一定在 mid 的左侧(包括 mid)。

基于此特性,我们可通过二分不断缩小区间,最终定位到唯一的峰顶下标。

二分查找的核心思路:基于“递增/递减段”划分区间

本题的核心是通过 arr[mid]arr[mid + 1] 的大小关系,判断 mid 所在的分段,进而确定峰顶的位置范围,逐步缩小区间。

1. 关键细节:边界初始化(为什么 left=1,right=arr.size()-2?)
题目明确给出两个关键条件:

  • 山脉数组严格递增后严格递减,且长度 ≥ 3;
  • 峰顶下标 i 满足 0 < i < arr.length - 1(即峰顶不可能是数组的第一个或最后一个元素)。

因此,我们无需从 0arr.size()-1 开始查找,直接将初始边界设为:

  • left = 1:排除第一个元素(不可能是峰顶);
  • right = arr.size() - 2:排除最后一个元素(不可能是峰顶)。

这一初始化不仅减少了不必要的查找次数,还避免了后续判断中“mid+1 越界”的风险(因 right 最大为 arr.size()-2mid 最大为 arr.size()-2mid+1 最大为 arr.size()-1,属于合法下标)。

2. 二段性划分与判断条件
arr[mid] < arr[mid + 1] 作为核心判断条件,将数组分为两段:

  • 满足条件(递增段)arr[mid] < arr[mid + 1] → 峰顶在 mid 右侧(mid + 1right);
  • 不满足条件(递减段)arr[mid] > arr[mid + 1] → 峰顶在 mid 左侧(leftmid)。

示例:若 arr = [0,10,5,2],取 mid = 1left=1, right=2):

  • arr[1] = 10arr[2] = 5,满足 arr[mid] > arr[mid + 1] → 峰顶在 left=1mid=1 之间,直接定位到峰顶。

3. 指针移动逻辑
根据上述二段性划分,指针移动规则如下:

  • arr[mid] < arr[mid + 1]mid 在递增段,峰顶一定在 mid 右侧,因此舍弃左侧区间(包括 mid),将 left 更新为 mid + 1
  • arr[mid] > arr[mid + 1]mid 在递减段,峰顶一定在 mid 左侧(或 mid 本身),因此舍弃右侧区间(不包括 mid),将 right 更新为 mid

关键逻辑:为什么 arr[mid] > arr[mid + 1]right = mid
因为 mid 有可能就是峰顶(例如 arr = [0,2,1]mid=1arr[1] > arr[2]mid 本身就是峰顶),若将 right 更新为 mid - 1,会漏掉峰顶,导致结果错误。

4. 循环结束条件
循环条件设为 left < right,当 left == right 时,区间缩小到唯一元素,该元素就是峰顶的下标,循环结束。

至于为什么无需进一步判断,是因为二分的每一步都严格根据“递增/递减段”缩小范围,最终 leftright 会收敛到唯一的峰顶下标,无需额外验证。

5. 中间值(mid)的取法
采用向下取整mid = left + (right - left) / 2(等价于 (left + right) // 2,避免整数溢出)。

为什么不用向上取整?
若用向上取整(mid = left + (right - left + 1) / 2),当区间长度为 2 时(如 left=1, right=2arr = [0,3,2]):

  • 计算 mid = 1 + (2-1+1)/2 = 2
  • arr[2] > arr[3](此处 arr[3] 不存在,实际 mid=2right 边界,arr[2] > arr[3] 不成立,实际判断为 arr[2] > arr[3] 会越界?不,实际 right 最大为 arr.size()-2mid 最大为 arr.size()-2mid+1arr.size()-1,合法)。
    更关键的问题是:当 left=1, right=2arr[1] < arr[2] 时(如 arr = [0,1,3,2]left=1, right=2):
  • 向上取整 mid=2,判断 arr[2] > arr[3](成立),则 right=2
  • 此时 left=1 < right=2,循环继续,再次计算 mid=1 + (2-1+1)/2=2,陷入死循环

而向下取整可避免此问题:

  • 同样 left=1, right=2mid=1 + (2-1)/2=1
  • arr[1] < arr[2],则 left=mid+1=2,此时 left==right,循环结束,无死循环。

代码实现

class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {// 边界初始化:排除首尾元素(不可能是峰顶)int left = 1, right = arr.size() - 2;// 循环缩小区间,直到left == right(定位到峰顶)while (left < right) {// 中间值向下取整,避免死循环int mid = left + (right - left) / 2;if (arr[mid] < arr[mid + 1]) {// mid在递增段,峰顶在右侧,舍弃左侧left = mid + 1;} else {// mid在递减段,峰顶在左侧(含mid),舍弃右侧right = mid;}}// 循环结束时,left == right,即为峰顶下标return left;}
};

总结:二分查找的关键细节复盘

  1. 边界初始化要结合题目条件:利用“峰顶不可能是首尾元素”的特性,将 left 设为 1、right 设为 arr.size()-2,减少查找次数并避免越界;
  2. 判断条件要紧扣“二段性”:通过 arr[mid]arr[mid+1] 的大小关系,明确 mid 所在的分段(递增/递减),进而确定峰顶的范围;
  3. 指针移动要避免“漏解”:当 arr[mid] > arr[mid+1] 时,right = mid(而非 mid-1),防止漏掉 mid 本身就是峰顶的情况;
  4. 中间值取整要匹配循环逻辑:因 left 可能更新为 mid+1right 可能更新为 mid,需用向下取整避免区间长度为 2 时的死循环。

这道题的核心是“利用局部单调性划分二段区间”,掌握这一思路后,面对旋转数组、分段有序数组等更复杂的查找问题,也能快速找到二分的判断条件和边界逻辑。

下一篇题目预告

本次我们通过“山脉数组峰顶索引”掌握了“局部单调性+二段性”的二分思路,下一篇将进阶讲解同类型但更灵活的题目——力扣162. 寻找峰值。该题中数组可能存在多个峰值,且边界可视为“无穷小”,需要在本次思路基础上调整判断逻辑,感兴趣的朋友可以提前浏览题目,思考如何用二分解决,我们下一篇详细拆解!

在这里插入图片描述

“Doro又又又带着小花🌸来啦!这道山脉数组的峰顶索引,是不是把‘局部单调性+二分二段性’的细节讲透了呀?不管是‘left从1开始、right到size-2结束’的边界技巧,还是‘mid向下取整避免死循环’的关键逻辑,只要把这些点吃透,以后遇到‘分段有序数组找目标’的题,就能快速找到突破口啦~

如果觉得这篇拆解帮你理清了二分的思路,别忘了点赞收藏呀!下次写二分题想不起来边界怎么设、指针怎么移时,翻到这篇就能快速回忆细节~

关注博主,后面还会一起攻克更多二分变形题,比如下一篇要讲的「寻找峰值」,从‘会写二分’到‘写对二分’,咱们一步一步慢慢进阶!”

http://www.dtcms.com/a/362779.html

相关文章:

  • ansible循环
  • GitHub Classroom:编程教育的高效协作方案
  • 从零开始的云计算生活——第五十七天,蓄势待发,DevOps模块
  • 数据量太大处理不了?Hadoop+Spark轻松解决海洋气象大数据分析难题
  • HQX SELinux 权限问题分析与解决
  • 使用 Avidemux 去除视频的重复帧
  • 亚马逊美加站点物流新规解读:库存处理逻辑重构与卖家应对策略
  • 两台电脑通过网线直连共享数据,设置正确,却互相ping不通的解决方法
  • 探索 UniHttp:解锁 Xml 及 JavaBean 序列化的多种方式
  • ASP.NET Core上传文件到minio
  • 嵌入式硬件 - 51单片机1
  • JVM中产生OOM(内存溢出)的8种典型情况及解决方案
  • 自从不小心踢了一脚主机之后,电脑频繁蓝屏、死机、无法开机……
  • 鸿蒙Next开发指南:XComponent与Progress组件的深度解析与实践
  • 睿思芯科正式加入龙蜥社区,携手共建 RISC-V 服务器生态新标杆
  • react+taro的使用整理
  • 【JavaEE】(21)Spring AOP
  • 解密GTH时钟架构:一网打尽收发器时钟之谜
  • 火语言 RPA 界面应用生成:低代码逻辑下的功能设计与场景适配
  • PowerPoint和WPS演示如何循环放映PPT
  • 想找Gamma的平替?这几款AI PPT工具值得试试
  • 从技术架构到经济价值:低代码在企业开发中的成本节约潜力
  • LeetCode 925.长按键入
  • 哈希表-面试题01.02.判定是否互为字符重排-力扣(LeetCode)
  • 趣味学RUST基础篇(HashMap)
  • 二叉树的非递归遍历 | 秋招面试必备
  • Spring Bean
  • LLM面试50问:NLP/RAG/部署/对齐/安全/多模态全覆盖
  • R语言根据经纬度获得对应样本的省份
  • WPF依赖属性和依赖属性的包装器: