文章目录
二分查找的基础
//二分查找的两种实现方式
#include<iostream>
#include<vector>
using namespace std;
// 第一种方法:[left,right]
// const int* p 和int* const p的区别第一种就是限定值 第二种是限定地址
// const int function(){} int function()const{} 前者返回的是const变量 后者是类内不能进行修改其成员的变量
vector<int>::const_iterator Er_search_one(vector<int>& num,int target)
{
//初始化
int left = 0, right = num.size()-1;
int middle;
//第一个重点判断是<还是<=,【left,right】,我们拿【1,1】做例子,发现=其实是可以存在的,此时就直接是1
while (left<=right)
{
//记得判断是否溢出
//由于加法存在溢出可能 middle = (left + right)/2;
middle = left + (right - left) / 2;
//第二个重点判断实际上就是判断是该用>还是>=,判断:想着下一次循环下变成【left,middle】,由于middle不是已经判断是不存在了嘛?所以就不需要等于了。
if (num.at(middle) > target)
right = middle -1;
else if (num.at(middle) < target)
left = middle + 1;
else
return num.cbegin() + middle;
}
//此处并不完全对,实际上后续可以判断一下是否等于cend 不然直接解引用会有错误。
return num.cend();
}
// 第二种方法:[left,right)
vector<int>::const_iterator Er_search_two(vector<int>& num,int target)
{
//初始化
int left = 0, right = num.size()-1;
int middle;
//第一个重点判断是<还是<=,[left,right),我们拿[1,1)做例子,发现=其实是不存在的,此时就无法等于
while (left < right)
{
//记得判断是否溢出
//由于加法存在溢出可能 middle = (left + right)/2;
middle = left + (right - left) / 2;
//第二个重点判断实际上就是判断是该用>还是>=,判断:想着下一次循环下变成[left,middle),由于本来就忽略了middle的判断,所以大胆等于
if (num.at(middle) > target)
right = middle;
else if (num.at(middle) < target)
left = middle + 1;
else
return num.cbegin() + middle;
}
//此处并不完全对,实际上后续可以判断一下是否等于cend 不然直接解引用会有错误。
return num.cend();
}
int main()
{
vector<int >vec{1,2,3,4,5,6,7,8};
cout<<"第一种写法:"<<*Er_search_one(vec,4)<<endl;
cout<<"第二种写法:"<<*Er_search_two(vec,4)<<endl;
return 0;
}
–写于2023/4/24
leetcode 704二分查找
这里实际上采用的是左闭右闭的方法,返回非迭代器。迭代器版本见上面。
成功代码:
class Solution {
public:
int search(vector<int>& nums, int target) {
//采用【1,1】
int left = 0, right = nums.size()-1, middle;
while(left<=right)
{
middle = left + (right - left) / 2;
if(nums[middle] < target)
left = middle + 1;
else if(nums[middle] > target)
right = middle - 1;
else
return middle;
}
return -1;
}
};
–写于2023/4/25
leetcode 35 搜索插入位置
此题的前期思路很简单,但是最重要的实际上就是关注的是当元素不存在的时候,我们该如何返回相对应的地址。
这里具体介绍一下个人的想法(例子数组[1,3,5,6]):
- 首先分类:
a. target在所有的数组之前,比如target = 0.
b. target在数组之中,且等于其中的一个值。如target = 1.
c. target在数组之中,且不等于其中任意一个值。如target =2.
d. target在数组的之后,比如target = 7. - 然后确定方法,明显二分查找。毕竟直接说是用二分查找的。hh
- 然后将上文所述的4中情况进行想一下,对应的答案应该如何用已知的变量进行描述。
b. 明显不需要进行讨论,直接二分可得。
a. 我们想象一下[1,3,5,6]进行二分走一遍变成[1,3]此时middle = 1;left = 0;right = 0;然后while循环还满足继续循环,此时middle = 0;left = 0;right =-1;又已知答案该是0,所以可以是left 或者right+1或者middle,见下一起进行统计判断。
c. 而对于其中的值的话,第一次还是middle = 1;left = 0;right = 0然后此时while条件仍然满足,继续middle = 0;left = 1;right = 0;又已知答案该是1,所以可以是left或者right+1或者middle+1
d.相同的方式分析,留给读者进行相对应的分析,可得 - 总结发现实际上大概的选择其实就是要么left或者right+1或者对应情况分析得到对应middle对应的return。
AC:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
//采用【1,1】
int left = 0, right = nums.size()-1, middle = 0;
while(left <= right)
{
middle = left + (right - left) / 2;
if(nums[middle] < target)
{
left = middle + 1;
}
else if(nums[middle] > target)
{
right = middle - 1;
}
else
return middle;
}
return left;
}
};
–写于2023/4/25
leetcode 34 在排序数组中查找元素的第一个和最后一个位置
实际上简单的思路很容易想出来,可以先处理完成后,再去分情况讨论。
AC:(值得关注的是传入【】空数组的时候的问题)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1, middle = 0;
//采用[1,1]的写法
while(left <= right)
{
middle = left + (right - left) / 2;
if(nums[middle] < target)
left = middle + 1;
else if(nums[middle] > target)
right = middle - 1;
else
break;
}
vector<int >vec{middle, middle};
int ltag = middle,rtag = middle;
if(nums.size() == 0 || nums[middle] != target)
{
vec[0] = -1;
vec[1] = -1;
return vec;
}
//进行再处理
while((nums[vec[0]]==nums[middle] && vec[0]>0) || (nums[vec[1]]==nums[middle] && vec[1] < (nums.size() - 1)))
{
if(vec[0] > 0 && nums[vec[0] - 1] == nums[middle])
vec[0] -= 1;
if(vec[1]<nums.size()-1 && nums[vec[1] + 1] == nums[middle])
vec[1] += 1;
if (vec[0] == ltag && vec[1] == rtag)
break;
ltag = vec[0];
rtag = vec[1];
}
return vec;
}
};
但问题在于,有没有什么更好的方式去实现这一做法?
我们发现我们是先排序完之后,再次进行情况判断,得出最后的结果,那么,有没有一种情况可以,让我们边取数字,边得到最后的答案吗?
这里就可以去看其对应的题解了,讲的还是挺清楚的,这里就不赘叙了。
–写于2023-4-27
leetcode 69 x的平方根
使用二分法进行查找,此题需要关注的是为什么需要使用unsigned,如果使用int会发生什么。希望读者进行思考一下。
class Solution {
public:
int mySqrt(int x) {
//采用二分法求取值,采用【1,1)的写法.由于上限是2的32次方,故右侧范围取不到2的16次方
unsigned int left = 0, right = 65536, middle = 0;
unsigned int num = 0;
//值得关注的是此处不适用等于
while (left < right)
{
//此处需要考虑的是溢出。
middle = left + (right - left) / 2;
if (middle * middle < x)
{
if (((middle + 1) * (middle + 1)) > x)
return middle;
//经典判断[left,right)中,left需要加1,因为[实际上多一次判断。
left = middle + 1;
}
else if ((middle * middle) > x)
right = middle;
else
return middle;
}
return -1;
}
};
答案:实际上就是在超过上限的时候,会转化为负数。
当然此题还有别的解法:
这边进行简述一下,官方题解一:实际上就是将计算问题变成数学问题,可以适当的去理解一下。官方题解三:就是在模拟逼近。
写于2023-5-3
leetcode 367 有效的完全平方数
实际上和上一题有异曲同工之妙,实际上上一题理解之后,这一题就简单了。
class Solution {
public:
bool isPerfectSquare(int num) {
//此处和之前的那一题一样,为了防止int的溢出,需要使用unsigned 或者longlong
unsigned int left = 0, right = 65536, middle = 0;
//此处用【1,1】或者【1,1)都可以,这里采用比较少的【1,1),锻炼一下
while (left < right)
{
middle = left + (right - left) / 2;
if ((middle * middle) < num)
left = middle + 1;
else if ((middle * middle) > num)
right = middle;
else
return true;
}
return false;
}
};
写于 2023-5-4