探秘二分查找:一道温柔的算法入门题!
大家好,我是忍者算法。今天我想和大家聊一道特别适合入门的算法题 - LeetCode 35「搜索插入位置」。这道题虽然看起来简单,但它蕴含了二分查找的精髓,是我最喜欢用来教学的题目之一。
📚 从图书馆说起
想象你在图书馆整理书架。手里有一本新书要插入已经按编号排序的书架中。你会怎么做?相信大多数人不会从头到尾一本本对比,而是会先看看中间的书,判断新书应该放在左边还是右边,这样反复几次就能找到正确的位置。
这正是二分查找的思想,也是我们今天要解决的问题!
💡 问题解析
题目要求:
给定一个排序数组和一个目标值,在数组中找到目标值并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
示例:
输入: nums = [1,3,5,6], target = 5
输出: 2 // 5在数组中,返回其位置2
输入: nums = [1,3,5,6], target = 2
输出: 1 // 2不在数组中,应该插入到位置1
🤔 思路发展历程
让我们看看解决这个问题时,思维是如何逐步进化的:
1. 最直观的思路(新手常用)
// 从头到尾遍历一遍
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= target) {
return i;
}
}
return nums.length;
这种方法虽然直观,但时间复杂度是O(n),没有利用数组已排序的特性。
2. 二分查找思路(进阶方案)
既然数组已经排序,我们可以用二分查找将时间复杂度降到O(log n)。
🚀 优雅的解决方案
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
// 特殊情况处理:目标值比所有元素都大
if (target > nums[right]) {
return nums.length;
}
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; // 目标在左半部分
}
}
// 没找到目标值时,left就是插入位置
return left;
}
}
📝 代码详解
让我们一步步理解这个解法:
1. 初始化
- left和right指针分别指向数组的开始和结束
- 先处理特殊情况,如果目标值比所有元素都大
2. 二分查找过程
- 每次取中间位置mid
- 比较nums[mid]和target:
- 相等:找到目标值,直接返回
- nums[mid]小于target:说明目标在右边
- nums[mid]大于target:说明目标在左边
3. 返回插入位置
- 循环结束时,left就是正确的插入位置
- 无需特别处理,因为left自然会停在正确位置
🎯 易错点分析
-
边界条件
- while循环的条件是 left <= right 而不是 left < right
- 这确保了不会漏掉任何一个位置
-
中间位置计算
- 使用 left + (right - left) / 2 而不是 (left + right) / 2
- 避免整数溢出的隐患
-
返回值选择
- 最后返回left而不是right
- left自然停在插入位置,而right总是在其左边
💡 举一反三
这道题的思路可以应用到很多类似场景:
-
查找第一个不小于目标值的位置
- 这就是我们这道题的本质
-
查找最后一个不大于目标值的位置
- 只需要稍微改变一下判断条件
-
在排序数组中查找元素的第一个和最后一个位置
- LeetCode 34题,是这道题的进阶版
🌟 面试技巧
-
理解二分查找的本质
- 不是简单的查找,而是在查找的过程中缩小范围
- 每次都能排除一半的搜索空间
-
处理边界情况
- 数组为空
- 目标值在数组范围之外
- 数组中有重复元素
-
代码优化
- 考虑代码的简洁性和可读性
- 处理好整数溢出问题
作者:忍者算法
公众号:忍者算法
🎁 回复【刷题清单】获取LeetCode高频面试题合集
🧑💻 回复【代码】获取多语言完整题解
💡 回复【加群】加入算法交流群,一起进步
#算法面试 #LeetCode #二分查找 #数组