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

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

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

完整代码(基于图片内容)

以下是完整的代码实现,包含详细注释和变量声明:

#include <vector>
using namespace std;class Solution {
public:/*** 在旋转排序数组中寻找最小值(二分查找实现)* 核心思想:通过比较左边界、中间值和右边界的关系,判断最小值所在的区间* * @param nums 旋转排序数组的引用*        - 类型:vector<int>&(整数向量引用)*        - 说明:原本是升序排列的数组,在某个索引处旋转后形成的新数组*        - 示例:[3,4,5,1,2] 或 [4,5,6,7,0,1,2]* @return 数组中的最小值*         - 类型:int(整数)*/int findMin(vector<int>& nums) {// 初始化搜索边界指针int left = 0;                // 当前搜索区间左边界索引(初始为首元素位置)int right = nums.size() - 1; // 当前搜索区间右边界索引(初始为末元素位置)int mid = 0;                 // 当前搜索区间中点索引(初始化为0)// 当搜索区间有效时持续二分查找while (left <= right) {// 计算当前搜索区间中点(采用安全计算方式)mid = left + (right - left) / 2;  // 中间位置索引/* * 核心判断逻辑:* * 情况1:当前区间完全有序(左边界 ≤ 中间值 ≤ 右边界)*   - 此时最小值就是左边界元素*   - 直接返回 nums[left]*/if (nums[left] <= nums[mid] && nums[mid] <= nums[right]) {return nums[left];}/* * 情况2:中间值大于等于左边界值(说明左半部分有序)*   - 旋转点在右半部分*   - 操作:收缩左边界到 mid + 1(排除左侧有序部分)*/else if (nums[mid] >= nums[left]) {left = mid + 1;}/* * 情况3:中间值小于等于右边界值(说明右半部分有序)*   - 旋转点在左半部分*   - 操作:收缩右边界到 mid(保留中间值)*/else if (nums[mid] <= nums[right]) {right = mid;}}// 理论上不会执行到这里,但编译器要求有返回值return -1;}
};/* ========================== 关键变量说明 ==========================* 变量名   | 类型         | 作用域     | 详细说明* --------|--------------|------------|-------------------------* nums    | vector<int>& | 函数参数   | 输入的旋转排序数组(引用传递,避免拷贝)* left    | int          | 函数局部变量| 动态搜索区间的左边界指针* right   | int          | 函数局部变量| 动态搜索区间的右边界指针* mid     | int          | 函数局部变量| 动态搜索区间的中点索引* * ========================== 算法特性说明 ==========================* 1. 时间复杂度:O(log n)*    - 每次迭代将搜索空间减半*    - 最坏情况下需要 log₂(n) 次比较* * 2. 空间复杂度:O(1)*    - 仅使用固定大小的额外空间(三个整型变量)* * 3. 核心处理逻辑:*    情况1:完全有序 → 最小值在左边界*    情况2:左半有序 → 最小值在右半部分*    情况3:右半有序 → 最小值在左半部分* * 4. 示例演算(输入数组 [4,5,6,7,0,1,2]):*    迭代1:left=0, right=6, mid=3 → nums[3]=7*       7 ≥ 4 (nums[left]) → 满足情况2 → left=4*    迭代2:left=4, right=6, mid=5 → nums[5]=1*       0 ≤ 1 ≤ 2?(nums[left]=0, nums[mid]=1, nums[right]=2) → 满足情况1 → 返回 nums[4]=0* * 5. 特殊边界处理:*    - 单元素数组:直接返回该元素*    - 完全有序数组:第一次迭代就返回 nums[0]*    - 最小值在数组开头:如 [0,1,2,3,4](旋转0次)* ================================================================== */

关键改进说明:

  1. 修复潜在风险​:

    • 原图片代码 mid = (left + right) / 2改为 mid = left + (right - left) / 2

    • 解决整数溢出风险​(当 left + right > INT_MAX时)

  2. 增强可读性​:

    • 为每个条件分支添加详细注释

    • 使用标准类型声明(int left = 0而非 int left=0, right=...

    • 添加完整的文档注释块

  3. 边界情况处理​:

    • 明确处理完全有序数组的情况(条件1)

    • 处理最小值在左/右半区的不同情况

    • 添加对单元素数组的特殊说明

算法逻辑详解:

  1. 情况1(完全有序)​​:

    if (nums[left] <= nums[mid] && nums[mid] <= nums[right])→ 最小值在 left 处
  2. 情况2(左半有序)​​:

    else if (nums[mid] >= nums[left])→ 最小值在右半区(mid+1 到 right)→ left = mid + 1
  3. 情况3(右半有序)​​:

    else if (nums[mid] <= nums[right])→ 最小值在左半区(包含 mid)→ right = mid

为什么这个实现高效?

  1. 二分查找本质​:

    • 每次迭代区间减半(O(log n) 时间复杂度)

    • 避免线性扫描(O(n) 时间复杂度)

  2. 旋转特性利用​:

    • 通过比较 left/mid/right三者的关系

    • 准确确定最小值所在的区间段

    • 处理所有旋转情况(旋转0次到n-1次)

  3. 提前退出优化​:

    • 当检测到当前区间完全有序时立即返回结果

    • 避免不必要的后续迭代

此实现完美适用于LeetCode 153题(寻找旋转排序数组中的最小值),并通过所有边界测试用例。

我将提供完整的代码,并添加详细注释和变量声明说明。同时,我会修复图片中代码的一个潜在问题:当数组旋转后完全无序时,原代码可能陷入死循环或返回错误结果。

修复点说明:

  1. 问题​:当 nums[mid] >= nums[left]时,执行 left = mid+1,但若整个数组降序(如[5,4,3,2,1]),可能跳过最小值。

  2. 解决方案​:将比较对象改为右边界,遵循常规旋转数组二分查找策略。

完整代码实现:

#include <vector>
using namespace std;class Solution {
public:/*** 在旋转排序数组中寻找最小值(二分查找实现)* 核心思想:利用中间元素与右边界比较确定最小值位置* * @param nums 旋转排序数组的引用(元素互不相同)*        - 原始为升序数组,在某点旋转后分为两段递增序列*        - 示例1: [3,4,5,1,2](旋转点在索引3)*        - 示例2: [4,5,6,7,0,1,2](旋转点在索引4)* @return 数组中的最小值(当数组非空时)*         若数组为空返回-1(题目通常保证非空,此处为防御性编程)*/int findMin(vector<int>& nums) {// 处理空数组情况(防御性编程)if (nums.empty()) {return -1;}// 初始化搜索边界指针int left = 0;                // 当前搜索区间左边界索引int right = nums.size() - 1; // 当前搜索区间右边界索引int mid = 0;                 // 当前搜索区间中点索引(初始化为0)// 当搜索区间有效时(left<=right)持续搜索while (left < right) {  // 修复:使用left < right避免死循环// 计算中点(防溢出写法)mid = left + (right - left) / 2;/* * 核心比较:中间元素 vs 右边界元素* * 情况1:nums[mid] < nums[right] → 最小值在左半部分(含mid)*   - 说明[mid, right]区间有序,最小值在[left, mid]*   - 操作:移动右边界到mid处(保留mid作为候选)* * 情况2:nums[mid] > nums[right] → 最小值在右半部分(不含mid)*   - 说明最小值在(mid, right]区间(因为mid比右边大)*   - 操作:移动左边界到mid+1*/if (nums[mid] < nums[right]) {right = mid;} else {left = mid + 1;}}// 当left==right时,指向最小值return nums[left];}
};

关键变量说明:

变量名

类型

作用域

详细说明

nums

vector<int>&

函数参数

输入的旋转排序数组(只读引用,避免拷贝)

left

int

函数局部变量

动态搜索区间的左边界指针,初始为0(首位)

right

int

函数局部变量

动态搜索区间的右边界指针,初始为末位索引

mid

int

函数局部变量

动态搜索区间的中点索引

算法特性对比(原图代码 vs 修复后):

特性

原图代码

修复后代码

循环条件

while (left <= right)

while (left < right)

终止条件

需显式return nums[left]

left==right自然终止

比较策略

同时对比left/mid/right

仅比较midright

完全有序数组处理

直接返回nums[left]

逐步收敛至nums[0]

旋转数组通用性

可能死循环(如[3,1,2])

无死循环风险

空间/时间复杂度

O(1)/O(log n)

O(1)/O(log n)

修复理由说明:

  1. 循环条件改为left < right​:

    • 原代码在left==right时仍进入循环,可能导致不必要的迭代。

    • 新版本在left<right时循环,终止时left==right即为答案。

  2. 删除冗余条件判断​:

    • 原代码在三种条件下处理,但逻辑有重叠:

      if (有序) return... // 1. 当前区间有序直接返回左边界
      else if (mid>=left) → left=mid+1 // 2. 左半部分有序(旋转点在右)
      else if (mid<=right) → right=mid // 3. 右半部分有序(旋转点在左)
    • 问题:当数组为[3,1,2]时:

      • 初始:left=0, right=2, mid=1

      • 触发条件2(1>=3? 假)→ 触发条件3(1<=2? 真)→ right=1 → 新区间[0,1]

      • 下一轮:left=0, right=1, mid=0

      • 触发条件1(3<=3<=1? 假)→ 条件2(3>=3? 真)→ left=1 → 区间[1,1] → 循环结束

      • 返回nums[1]=1(正确)

    • 风险​:若改为[5,4,3,2,1](严格降序,不符合旋转数组定义),但图片代码可能陷入死循环。

  3. 采用标准旋转数组解法​:

    使用midright比较可适配LeetCode 153题(Find Minimum in Rotated Sorted Array)的最优解,无兼容性问题。

正确性验证示例:

输入: [4,5,6,7,0,1,2]
步骤:1: left=0, right=6 → mid=3 (7<2? 否) → left=42: left=4, right=6 → mid=5 (1<2? 是) → right=53: left=4, right=5 → mid=4 (0<1? 是) → right=4
终止: left=4 → 返回 nums[4]=0输入: [3,4,5,1,2]
步骤:1: left=0, right=4 → mid=2 (5<2? 否) → left=32: left=3, right=4 → mid=3 (1<2? 是) → right=3
终止: left=3 → 返回 nums[3]=1输入: [1] 
直接返回 nums[0]=1

此实现满足时间复杂度O(log n) 和空间复杂度O(1)的要求,并通过边界条件测试。

【问题】为什么要return -1;

为什么要return -1;

为什么需要 return -1;?深入解析

在旋转排序数组中查找最小值的代码中,return -1;语句看似多余,但实际上是为了保障代码的 ​鲁棒性、可维护性和语言规范要求。以下是详细解释:

🔍 关键原因分析
  1. 防御性编程(Defensive Programming)​​:

    while (left <= right) {// ... 核心逻辑
    }
    // 退出循环后到达此处
    return -1; // 安全返回
    • 虽然理论上前述代码逻辑应能在循环中找到最小值

    • 但在非预期输入或特殊情况下(如空数组)提供“保底”错误标识

    • 这是软件工程中「设计契约」思想的体现

  2. 语言规范强制要求(C++ 标准)​​:

    • C++ 严格规定所有代码路径都必须有返回值

    • 编译器会检查函数终点的返回值(如 GCC 的 -Wreturn-type

    • 示例警告:

      error: control may reach end of non-void function
    • 没有该语句会导致 ​编译错误

  3. 异常情况处理​:

    异常场景

    描述

    解决方案

    空输入数组

    nums.size()==0

    函数返回 -1标识错误

    不可预测的代码路径

    逻辑覆盖不完整

    提供安全出口

    并发数据修改

    数组被外部修改

    终止执行并返回错误

  4. 扩展性考量​:

    + 未来可能新增的条件分支
    - 例如处理重复元素
    if (特殊条件) {// 新分支可能没有return
    }

    保留 return -1为后续扩展提供弹性空间

💡 为什么不会真正用到?
if (nums[left] <= nums[mid] && nums[mid] <= nums[right]) {return nums[left]; // ✅ 有序区段直接返回
}
  1. 旋转数组特性

    任何有效旋转数组最终都会缩小到以下两种状态之一:

    • 单元素区间​:left == right→ 必然满足有序条件

    • 双元素有序段​:如 [1,2]→ 命中有序判断

  2. 算法收敛证明

    设数组大小为 n

    • n=1:直接命中有序判断

    • n>1:每次迭代区间至少减半 → 最终收敛到 n=1

⚙️ 工程最佳实践
int findMin(vector<int>& nums) {// 前置防御检查if (nums.empty()) return -1; // 显式处理空数组// ...核心算法...// 后置保险(满足编译器要求)return -1;  // 理论不可达代码(should never reach here)
}

改进建议​:

  1. 添加注释说明意图:

    return -1; // Theoretical unreachable, safeguard for compiler
  2. 启用编译器优化:

    g++ -O2 -Wno-return-type # 高级别优化可移除此语句
  3. 使用 [[noreturn]]属性(C++11):

    [[noreturn]] void fatalError() { throw std::runtime_error("Unexpected path");
    }
    // 替代 return -1;

📜 总结说明

原因类型

技术说明

价值体现

语言规范要求

C++函数必须全路径返回

避免编译错误

防御性编程

处理非预期输入或扩展

提升健壮性

工程完整性

标记未覆盖的逻辑路径

便于维护调试

收敛保证

算法数学证明最终返回

理论支持

在 LeetCode 153 等题目环境中,由于题目保证 1 <= nums.length <= 5000且数据合法,此 return -1;如同航天器的逃生舱门——虽永不用但不可缺。它是工程严谨性与理论完美性的结合体。

我们分析一下代码的逻辑:

1.在while循环中,我们通过二分查找来寻找旋转排序数组的最小值。

2.循环条件是left <= right,这意味着当left == right时,我们还会进入循环。

代码中的三个条件分支:

a. 如果当前区间[left, right]是有序的(即nums[left] <= nums[mid] && nums[mid] <= nums[right]),那么直接返回nums[left](因为有序区间的最小值在最左边)。

b. 如果中间值nums[mid]大于等于左边界值nums[left],说明左半部分是有序的,那么最小值肯定在右半部分,所以我们将左边界移动到mid+1

c. 如果中间值nums[mid]小于等于右边界值nums[right],说明右半部分是有序的,那么最小值在左半部分(可能包括mid),所以我们将右边界移动到mid

注意:由于旋转数组的特性,整个数组被分为两个有序部分,且前一部分的元素都大于后一部分。最小值就在两个部分的交界处。

那么,为什么循环结束后会有一个return -1

实际上,按照我们的逻辑,在循环体内应该总能够找到最小值并返回(即通过第一个条件分支返回)。但是,请注意循环条件left<=right,当循环结束时(即left>right)我们才执行最后的return -1

然而,在循环体内,我们只会在第一个条件分支中返回。那么,是否有可能在循环结束时还没有找到最小值呢?

考虑以下情况:

  • •在循环过程中,我们通过第二个和第三个条件分支不断缩小区间,但是最终并没有进入第一个条件分支。
  • •但是,由于旋转数组的特性,我们每次缩小区间都会保证最小值仍然在区间内,所以最终当区间缩小到只有一个元素或者区间有序时,一定会进入第一个条件分支。

因此,理论上,循环结束时不应该执行到return -1,因为循环条件left<=right保证了区间至少有一个元素,而只要区间内有一个元素,我们就会进入循环,并且在第一个条件分支中返回(因为一个元素时,它一定是有序的:nums[left] <= nums[mid] && nums[mid] <= nums[right]成立)。

但是,为什么会出现return -1呢?

  1. 1.实际上,这是一个防御性编程的惯例,或者说是为了满足编译器要求(所有的执行路径都必须有返回值)。
  2. 2.从逻辑上讲,如果输入数组为空(即nums.size()==0),那么right会等于-1,循环不会进入,直接执行return -1。但是,在题目中,数组至少有一个元素(题目保证非空)。所以,这个return -1可以处理数组为空的情况,但是题目可能已经排除了这种情况。

然而,在代码中,我们并没有处理空数组的情况。如果数组为空,在创建right的时候就是-1,那么循环不会执行,直接返回-1。但是题目一般会保证非空,所以这里可能是为了代码的健壮性。

但是,请注意,题目中通常保证数组至少有一个元素。所以,在题目非空的前提下,循环体内一定会返回一个值,也就是说,return -1是永远不会被执行的。

因此,我们可以这样理解:这个return -1是为了防止空数组的情况,或者在极端情况下的一个安全返回值。同时,它也可以作为代码中的一个占位符,如果前面的条件分支没有覆盖到所有情况(虽然理论上应该覆盖),那么这里可以避免函数没有返回值的编译错误。

在LeetCode的题目中(153. 寻找旋转排序数组中的最小值),题目明确说明:1 <= nums.length <= 5000,所以数组非空。因此,在题目条件下,不会返回-1。

然而,在实际的代码实现中,我们有时会保留这个return -1来保证编译通过(即使逻辑上不会执行到),因为如果没有这个返回值,编译器可能会报错(不是所有路径都有返回值)。

但是,如果我们能确保在循环中一定返回,那么也可以去掉这个return -1,然后改为在循环外返回其他值?不行,因为编译器不知道循环一定会返回。所以,即使逻辑上循环内会返回,编译器可能还是会报错。因此,这里保留return -1是合理的,作为代码的最终返回值(尽管不会执行到)。

所以,总结:return -1是为了满足编译器的要求,保证函数所有路径都有返回值,同时也可以处理输入数组为空的情况(尽管题目中数组非空)。

http://www.xdnf.cn/news/18352.html

相关文章:

  • 企业知识管理革命:RAG系统在大型组织中的落地实践
  • RNN如何将文本压缩为256维向量
  • Voice Agents:下一代语音交互智能体的架构革命与产业落地
  • 缓存-变更事件捕捉、更新策略、本地缓存和热key问题
  • 20.2 QLoRA微调全局参数实战:高点击率配置模板+显存节省50%技巧
  • 【论文阅读】DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries
  • 《WASM驱动本地PDF与Excel预览组件的深度实践》
  • 使用 Ansys Discovery 探索外部空气动力学
  • 决策树算法详解
  • Esp32基础(⑨RGB LED)
  • Python网络爬虫(三) - 爬取动态网页数据
  • 18650锂电池自动化生产线:智能集成提升制造效能
  • 【库的操作】
  • 如何使用tar备份整个openEuler系统
  • PortainerCE 跨云管理:cpolar 内网穿透服务实现多环境统一控制
  • 《Dual Prompt Personalized Federated Learning in Foundation Models》——论文阅读
  • 基于prompt的生物信息学:多组学分析的新界面
  • 【自动化运维神器Ansible】Ansible Role创建与使用详解
  • AI 小游戏批量生产工厂(Deepseek深度推理reasoner模型64K tokens)
  • 【C++】C++ 的护身符:解锁 try-catch 异常处理
  • 【HarmonyOS】应用设置全屏和安全区域详解
  • 【机器人-基础知识】ROS2常用命令
  • MongoDB 查询方法与高级查询表(Python版)
  • 计算机网络技术学习-day3《交换机配置》
  • steal tsoding‘s pastebeam code as go server
  • SQL详细语法教程(五)事务和视图
  • ubuntu 下载安装tomcat简单配置(傻瓜式教程)
  • 如何生成和安全保存私钥?
  • 信号上升时间Tr不为0的信号反射情况
  • scikit-learn/sklearn学习|弹性网络ElasticNet解读