在做编程题的时候,我们要考虑算法的时间复杂度和空间复杂度,为了降低时间复杂度,我们经常用二分法将线性复杂度降为对数复杂度,那么现在我来总结一下二分查找。
二分查找
思路
当需要从线性数组查找时,我们可以遍历数组,时间复杂度为O(n)。但如果数组是有序的,那么我们可以先将数组中间的元素与待查找的元素比较,根据比较结果从数组的前半部分或者后半部分继续查找,这样显然要比逐个查找快多了。在最坏情况下,遍历法一次比较排除一个元素,因此最多需要比较n次,二分法一次比较排除一半元素,因此最多需要比较log n次。
代码
class Solution {
public:
int binarySearch(vector<int>& nums, int target) {
int left = 0, right = nums.size();
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid;
}
return left;
}
};
虽然思路很简单,但实际写出来的代码有许多地方需要注意的。首先在逻辑上变量right指向的并不是有效位置,在处理数组的时候往往会取左闭右开的区间。然后当left < right时继续迭代,实际上最后循环总会在left = right时结束,因此最后返回哪一个都是一样的。接着是mid的计算方法,理论上和(left + right) / 2得到的结果是一样的,但在left和right均没有溢出的情况下left + right有可能溢出,虽然采用long long int类型也可以解决问题,但如果left和right本身就是long long int类型那就没有办法了,所以我选择换一种方法计算。比较有三种结果,我只分两种情况讨论,因为多一次判断会让时间复杂度更高。当nums[mid] < target时,说明target在nums[mid]和nums[right]之间,由于left在逻辑上是有效位置而nums[mid]已经排除,因此令left = mid + 1;当nums[mid] >= target时,说明target在nums[left]和nums[mid]之间,由于right在逻辑上是无效位置而nums[mid - 1]未排除,因此令right = mid。最后还要注意,如果数组中不存在目标元素,函数会返回不小于目标元素的最小元素。在实际运用中,我们可以根据问题需要进行细节的调整,最关键是是要保证函数不能进入死循环,这是因为整数除法会向下整取,所以当left和right相邻时,mid会取到left,一定要检验在这种情况下迭代能否结束。
总结
二分查找是一种非常优秀的算法,在遇到有序数组或者需要降低时间复杂度的时候一定要优先考虑,同时一定要注意细节。
本文深入讲解了二分查找算法的基本思想及其应用场景,并提供了一个具体的C++实现案例。通过对比线性查找,阐述了二分查找如何将时间复杂度从O(n)降低到O(log n),并详细解释了实现过程中需要注意的关键细节。
1845

被折叠的 条评论
为什么被折叠?



