数组之二分查找
“工欲善其事,必先利其器”
算法与数据结构是每一位程序员的基本功,最近学得比较杂,脑海里思路比较混乱,所以想通过记录的方法来理清一个思绪,帮助自己更好地理解算法。
今日知识点
数组理论基础:
- 存放相同类型的数据;
- 对于数组来说,在内存上一定是连续的,包括二维数组;
- 数组元素不能删除,只能覆盖;
今日题目
- 二分查找法
LeeCo 704: 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
先分析一波:二分查找法的原理比较简单,无非就是找到区间中点,然后找到所求值的那一半,再划分中点,不断进行下去。它真正的易错点在于它边界的划分,左端点要+1还是-1,右端点呢?
想要解决这个问题,首先是要判断区间定义,大概分为两种:左闭右闭和左闭右开[left, right] [left, right) 根据两种情况,有不同的写法
第一种情况:两侧都是闭区间
- while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
第二种情况:左闭右开
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
对应的代码是
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
除此之外,二分查找法还有额外的模版,这个模版也分为两种,分别是浮点数版和整数版
浮点数版大致原理如下:
double l = -1000, r = 1000;
while(r - l <= 1e-8)//这个地方有一个技巧就是,看保留几位小数,比他多两位即可
{
double mid = r + l / 2;
if(mid*mid*mid >= x)//判断条件
r = mid ; else l = mid;
}
printf("%d", l);
整数版大致原理如下
int x;
scanf("%d", &x);
int l = 0, r = n-1;
while(l >= r) return;
while(l < r)
{
//找左端点
int mid = l + r >> 1;
if(q[mid] >= x)
r = mid;
else l = mid + 1;
}
if(q[l] != x)
//按理说左侧端点就应该在迭代到最后的时候,为最小值就是x
cout<<"-1 -1"<<endl;
else {
cout<<l<<' ';
int l = 0, r= n - 1;
//找右端点
while(l < r)
{
int mid = r + l + 1>>1;
if(q[mid] <= x) l = mid;
else r= mid - 1;
}
cout<<l<<endl;
}