【题目链接】
35. 搜索插入位置
【题目描述】
【题解】
通过题目可以知道这是一道经典的二分查找的题目,对于二分查找的题目,根据需要查找的两个边界点,分为两个不同的模板,如下图所示。
这道题要求在数组中查找目标值并返回其索引,若目标值不存在,则返回它按顺序插入的位置。因此,它适合使用绿色边界点 对应的二分查找模板。
该模板适用于查找最小满足条件的值的场景,常用于定位满足特定条件的最小值或区间的左边界。其核心逻辑是通过不断收缩右边界,最终定位到符合条件的最小值。
其模板如下:
cpp
bool check(int x) {/* ... */} // 检查x是否满足某种性质
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
【AC代码】
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size();
while(l < r) {
int mid = l + r >> 1;
if(nums[mid] >= target)
r = mid;
else
l = mid + 1;
}
return l;
}
};
【思考】
1.能否使用红色边界点对应的模板呢?
这道题同样可以用红色边界点对应的模板 来解答,只是需要先将问题转化为寻找最后一个小于目标值的元素位置 ,再通过推导得出插入位置。
该模板的核心是定位最后一个满足条件的位置 (即右边界),而原问题实际需要的是第一个大于等于目标值的位置 (即左边界)。两者的转化逻辑很明确:插入位置 = 最后一个小于目标值的位置 + 1
,而寻找最后一个小于目标值的位置恰好是红色边界点模板的典型应用场景。
其模板如下:
cpp
bool check(int x) {/* ... */} // 检查x是否满足某种性质
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
【AC代码】
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r + 1) / 2;
if (nums[mid] < target) {
l = mid;
} else {
r = mid - 1;
}
}
// 循环结束后,l是最后一个可能<target的位置,需判断是否真的满足
if (nums[l] < target) {
return l + 1; // 存在<target的元素,插入位置为l+1
} else {
return l; // 所有元素≥target,插入位置为l(即第一个≥target的位置)
}
}
};
2.模板的两个边界l
和r
如何确定?
在ac之前,我卡在了一个地方,r
一直取的值是r = nums.size() - 1
,这导致在测试样例时,示例1和示例2都可以通过,但输入3确报了错。通过调试输出排查后发现,问题的根源在于边界值的设置。
当r = nums.size() - 1
时,示例1:
l = 0 , r = 3 , m i d = 1 , n u m s [ m i d ] = 3 , 3 < t a r g e t , l = m i d + 1 = 1 + 1 = 2 ; l=0,r=3,mid=1,nums[mid]=3,3<target,l=mid+1=1+1=2; l=0,r=3,mid=1,nums[mid]=3,3<target,l=mid+1=1+1=2;
l = 2 , r = 3 , m i d = 2 , n u m s [ m i d ] = 5 , 5 ≥ 5 , r = 2 ; l=2,r=3,mid=2,nums[mid]=5,5≥5,r=2; l=2,r=3,mid=2,nums[mid]=5,5≥5,r=2;
l = 2 , r = 2 , l=2,r=2, l=2,r=2,退出循环,返回l=2,答案正确 。
当r = nums.size() - 1
时,示例2:
l = 0 , r = 3 , m i d = 1 , n u m s [ m i d ] = 3 , 3 > t a r g e t , r = 1 ; l=0,r=3,mid=1,nums[mid]=3,3>target,r=1; l=0,r=3,mid=1,nums[mid]=3,3>target,r=1;
l = 0 , r = 1 , m i d = 0 , n u m s [ m i d ] = 1 , 1 < t a r g e t , l = m i d + 1 = 0 + 1 = 1 ; l=0,r=1,mid=0,nums[mid]=1,1<target,l=mid+1=0+1=1; l=0,r=1,mid=0,nums[mid]=1,1<target,l=mid+1=0+1=1;
l = 1 , r = 1 , l=1,r=1, l=1,r=1,退出循环,返回l=1,答案正确 。
当r = nums.size() - 1
时,示例3:
l = 0 , r = 3 , m i d = 1 , n u m s [ m i d ] = 3 , 3 < t a r g e t , l = m i d + 1 = 1 + 1 = 2 ; l=0,r=3,mid=1,nums[mid]=3,3<target,l=mid+1=1+1=2; l=0,r=3,mid=1,nums[mid]=3,3<target,l=mid+1=1+1=2;
l = 2 , r = 3 , m i d = 2 , n u m s [ m i d ] = 5 , 5 < t a r g e t , l = m i d + 1 = 2 + 1 = 3 ; l=2,r=3,mid=2,nums[mid]=5,5<target,l=mid+1=2+1=3; l=2,r=3,mid=2,nums[mid]=5,5<target,l=mid+1=2+1=3;
l = 3 , r = 3 , l=3,r=3, l=3,r=3,退出循环,返回l=3,答案错误 。
通过上述分析可以发现,r = nums.size()
的设置确保了搜索区间覆盖所有可能的插入位置,而r = nums.size() - 1
会漏掉插入到数组末尾的情况 ,导致部分测试用例失败。
二分查找的核心是明确搜索区间的定义,并确保区间覆盖所有可能的解。