算法:Binary Search

在做编程题的时候,我们要考虑算法的时间复杂度和空间复杂度,为了降低时间复杂度,我们经常用二分法将线性复杂度降为对数复杂度,那么现在我来总结一下二分查找。

二分查找

思路

当需要从线性数组查找时,我们可以遍历数组,时间复杂度为O(n)。但如果数组是有序的,那么我们可以先将数组中间的元素与待查找的元素比较,根据比较结果从数组的前半部分或者后半部分继续查找,这样显然要比逐个查找快多了。在最坏情况下,遍历法一次比较排除一个元素,因此最多需要比较n次,二分法一次比较排除一半元素,因此最多需要比较log n次。

代码

class Solution {
public:
    int binarySearch(vector& 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得到的结果是一样的,但在leftright均没有溢出的情况下left + right有可能溢出,虽然采用long long int类型也可以解决问题,但如果leftright本身就是long long int类型那就没有办法了,所以我选择换一种方法计算。比较有三种结果,我只分两种情况讨论,因为多一次判断会让时间复杂度更高。当nums[mid] < target时,说明targetnums[mid]nums[right]之间,由于left在逻辑上是有效位置而nums[mid]已经排除,因此令left = mid + 1;当nums[mid] >= target时,说明targetnums[left]nums[mid]之间,由于right在逻辑上是无效位置而nums[mid - 1]未排除,因此令right = mid。最后还要注意,如果数组中不存在目标元素,函数会返回不小于目标元素的最小元素。在实际运用中,我们可以根据问题需要进行细节的调整,最关键是是要保证函数不能进入死循环,这是因为整数除法会向下整取,所以当leftright相邻时,mid会取到left,一定要检验在这种情况下迭代能否结束。

总结

二分查找是一种非常优秀的算法,在遇到有序数组或者需要降低时间复杂度的时候一定要优先考虑,同时一定要注意细节。

你可能感兴趣的:(算法)