C++:数组-二分法-Leetcode-34题在排序数组中查找元素的第一个和最后一个位置
二分法的熟练应用,记录笔记,记住易错点
题目
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
知识点
二分法的写法规则,二分法的应用
思路分析
1 .首先根据题目分析几种可能出现的情况,以nums[5,7,7,8,8,10]为例。
- 当nums长度为0,返回{-1,- 1}
- 当target = 0时,返回{-1,-1}
- 当target = 12时,返回{-1,-1}
- 当target = 6时,返回{-1,-1}
- 当target = 8时,返回对应的结果,{3,4}
2.题目要求时间复杂度为 O(log n) 的算法,并且为升序数组,故求左边界和求右边界时都要使用二分法
3.总体思路为当nums[middle]小于target时,left指针不断的往右移,当nums[middle]大于target时,right指针不断的往左移,直至出现left与right指针出现交叉,或者left、right移出数组时,退出循环,返回对应的指针,求左边界返回左指针,求右边界时返回右指针。
4.具体分析:
- nums[5,7,7,8,8,10]
- target = 0时,求左边界:右指针不断往左靠,直至right=-1,退出循环,返回left = 0;求右边界:同样也是右指针往左靠,直至right = -1,退出循环,返回right = -1;
- target = 12时,求左边界:左指针不断往右靠,直至left = 6,退出循环,返回left = 6;求右边界:同样也是左指针不断往右靠,直至left = 6,退出循环,返回right = 5;
- target = 6时,当nums[middle]>target,右指针左移,right = middle-1;当nums[middle]<target,左指针右移,left = middle+1;
求左边界时:当nums[middle]==target时,右指针左移,因为求的左边界,right = middle-1,最终left=1,right=0,左右指针出现交叉,退出循环,返回left = 1;
求右边界时:当nums[middle]==target时,左指针右移,因为求的有边界,left = middle+1,最终left=1,right=0,左右指针出现交叉,退出循环,返回right = 0;
最终结果的事left = 1,right=0,出现了交叉,因为左右指针都会不断的移动去比较比target大或小的元素,直至满足情况。故需要再加一个判断left > right来去除此种情况,最终返回{-1,-1},因为target = 6不存在。 - target = 8时,求左边界和求右边界的判断逻辑与target = 6同理,也是直至最后出现左右指针出现交叉情况时,退出while(left<=right)的循环,然后求左边界返回left,求右边界返回right。
代码
/*
leetcode-34题二分法
思路分析:
1.先判定几种未找到值的情况
2.分别用二分法来求左边界和右边界
3.求左边界时,右边指针不断往左移,最终返回右指针
3.求右边界时,左边指针不断往右移,最终返回左指针
*/
#include "iostream"
#include "vector"
using namespace std;
class Solution
{
private:
//获取左边界
int getLeftBorder(vector<int> &nums, int target)
{
int left = 0;
int right = nums.size() - 1;
int middle;
while (left <= right)
{
middle = left + (right - left) / 2;
if (nums[middle] > target)
{
right = middle - 1;
}
else if (nums[middle] < target)
{
left = middle + 1;
}
else //nums[middle]==target
{
right = middle - 1;
}
}
//循环结束后
return left;
}
//获取右边界
int getRightBorder(vector<int> &nums, int target)
{
int left = 0;
int right = nums.size() - 1;
int middle;
while (left <= right)
{
middle = left + (right - left) / 2;
if (nums[middle] > target)
{
right = middle - 1;
}
else if (nums[middle] < target)
{
left = middle + 1;
}
else
{
left = middle + 1;
}
}
return right;
}
public:
vector<int> searchRange(vector<int> &nums, int target)
{
int len = nums.size();
if (len == 0)
{
return {
-1, -1};
}
int left = getLeftBorder(nums, target);
int right = getRightBorder(nums, target);
if (left > nums.size() - 1 || right < 0 || left > right) //left指针向右移动到最尾,right指针向左移动到最头,那就是没有找到
{
return {
-1, -1};
}
return {
left, right};
}
};
int main(int argc, const char **argv)
{
vector<int> nums;
nums.push_back(5);
nums.push_back(7);
nums.push_back(7);
// nums.push_back(7);
nums.push_back(8);
nums.push_back(8);
nums.push_back(10);
Solution s1;
vector<int> res;
res = s1.searchRange(nums, 6);
for (vector<int>::iterator it = res.begin(); it != res.end(); it++)
{
cout << *it << endl;
}
return 0;
}
总结
考察对二分法的熟练程度,本题可以求左右边界一块写,但初学者为了更好的理解,还是分开二分法求左右边界好,还是要多练,多调试理解原理。