二分查找作为一种非常高效的查找算法,是非常值得我们学习和掌握的。
首先二分查找是基于一个排序数组或者一个排序数组经过一定规则改变过的数组,这个是使用二分查找的前提。
普通的二分查找,比如在排序数组中找一个数,算法通过不断的以二倍的方式缩小待查找的区间,所以提高查找的效率到O(long n)
举例普通的二分查找
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 注意
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
else if (nums[mid] < target)
left = mid + 1; // 注意
else if (nums[mid] > target)
right = mid - 1; // 注意
}
return -1;
}
- 对一个数组的操作首先要保证不能越界,这个是编码的大忌,一般用right=length-1 比较好,因为有的特殊需求,要也要将下标为mid和left和right做元素做比较,如果用right=length会越界的。
- 接下来就关心的是while判断是< 还是 <= 了 ,如果使用< 的话 在left=right的时候循环就会退出,使用 left<=right在left=right的循环不会退出,再运行一次,想象一下,如果循环体里面有 left =mid+1;的操作本来已经达到目的了,整不好这一下子就能越界,或者运行超时,总的来说,应该根据自己画图的例子决定要不要用 <=。
- 上面简单的我就不画图演示了,相信大家都会,下面通过两个例题带大家深入的了解二分查找的算法细节。
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
这个题也能使用二分查找,解决一道一眼看不出答案的题怎么办,那就看两眼?。应该拿出纸笔,画出具体的例子,用脑子模拟机器做一遍。(高手不需要,高手也不会搜二分查找的^_^)。
话不多说,看图。
class Solution {
public:
int minNumberInRotateArray(vector<int> rotateArray) {
int length = rotateArray.size();
if (length == 0)
return 0;
int left = 0;
int right = length - 1;
while (left < right)
{
int mid = (left + right) / 2;
if (left == mid)
{
return rotateArray[left + 1];
}
if ((rotateArray[left] >= rotateArray[mid]) && (rotateArray[right] >= rotateArray[mid]))
{
right = mid;
continue;
}
if ((rotateArray[left] <= rotateArray[mid]) && (rotateArray[right] <= rotateArray[mid]))
{
left = mid;
continue;
}
}
return 0;
}
};
我发现其实,代码就是例子堆起来的,没有人能够一下子考虑到所有的情况,关键还是勤动笔,不只要脑子想,很容易晕头。
https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
这道题也是二分查找的升级呀,直接上图吧。
还是一句话,应该用具体的例子去堆积出代码,而不是用代码去检验例子,上述的例子的解法不唯一,比如也可以将目标值包含进区间里,只需要根据你的规则,画图,堆积代码就行。
前面说的< 和 <= 的区别,在我们这道题里面,==的时候就必须退出了,因为要使用相等时候的left值。所以如何使用还得自己自己分析。
class Solution {
public:
int leftfind(vector<int>& nums, int target)
{
int length = nums.size();
if (length==0)
{
return -1;
}
int left = 0;
int right = length;
while (left < right)//会引起left=right的时候跳出,left位没有被检测
{
int mid = (left + right) / 2;
if (nums[mid] == target)
{
right = mid;
}
else if (nums[mid] > target)
{
right = mid;
}
else if (nums[mid] < target)
{
left = mid + 1;
}
}
if (left==length)
{
return -1;
}
return nums[left] == target ? left : -1;
}
int rightfind(vector<int>& nums, int target)
{
int length = nums.size();
if (length == 0)
{
return -1;
}
int left = 0;
int right = length;
while (left < right)//左开右闭
{
int mid = (left + right) / 2;
if (nums[mid] == target)
{
left = mid + 1;//往右边赶
}
else if (nums[mid] < target)
{
left = mid + 1;
}
else if (nums[mid] > target)
{
right = mid;
}
}
if (left == 0)
{
return -1;
}
return nums[left - 1] == target ? left - 1 : -1;
}
vector<int> searchRange(vector<int>& nums, int target)
{
//二分查找,寻找左边界
int left = leftfind(nums, target);
int right = rightfind(nums, target);
vector<int> ret;
if ((left==-1) ||(right==-1))
{
ret.push_back(-1);
ret.push_back(-1);
}
else
{
ret.push_back(left);
ret.push_back(right);
}
return ret;
}
};