二分查找相关题目

前提条件

数组是有序数组
数组中无重复元素(否则下表不唯一)

区间

区间的定义:就是不变量while寻找中每一次边界的处理都要坚持根据区间的定义来操作,循环不变量规则。
通常有两种,一[left,right] 二[left, right)

结果

class solution{
	public:
	int search(vector<int>& nums, int target){
	int left = 0;
	int right = nums.size() - 1; //由于是左闭右闭的因此需要减1
	while (left <= right){//这块其实一直是成立的,左边永远小于右边,只有return才能真正意义上跳出这个循环(循环不变量原则)
		int mid = left + ((right - left) / 2);
		if (nums[mid] < target){//数组加索引
			left = mid + 1;
			}
		else if (nums[mid] > target){
			right = mid - 1;}
		else 
			return mid;	
	}
	return -1;
  }
};

注意几个语言层面的问题,如何跳出while循环?
①break:跳出这整个循环
②continue:跳出本次循环
③return:跳出函数,结束这个函数
④exit:结束这个程序,要求包含stdlib.h

class Solutin{
	public:
	int search(vector<int>& nums, int target){
		int left = 0;
		int right = nums.size(); //这样做相当于定义target在左闭右开的区间,[right, left)
		while (left < right){ //这里要使用<
			int mid = left + ((right - left) >> 1);
			if (nums[mid] == target)
				return mid;
			else if (nums[mid] > target)
				right = mid; //注意这里是左闭右开的区间,如果mid-1的话就会少一个元素
			else
				left = mid + 1;//左闭又开区间因此要+1
		}
	return -1;
	}
};

注意上述两种方法的区间,区间不同会导致循环体内部的代码逻辑不同。
除此之外,如何理解(right - left) >> 1?
二进制右移相当于除以2,left+((right - left) >> 1 )== left+((right-left)/2) ==(left+right)/2
那么为什么不直接用(right+left)/2呢?因为如果很长的话会造成溢出,同时位运算>>比/运算快。

题:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。

上述题目看到有序数组,以及无重复元素两个关键信息可以考虑用二分查找的方法来做。那么问题来了,根据上面的例子我们知道了二分查找可以用来寻找元素的位置,那么怎么来添加位置呢?

二分查找的关键在于要坚持循环不变量原则,对于添加元素而言,当不变量被打破时,就意味着原数组中不存在target。
那么何时会打破不变量呢?
对于[left , right]而言,当right < left时或者 left > right时,自然打破了。
对于[left, right)而言,当right == left时,自然也被打破了。
那么打破之后,我们该怎么返回索引值呢?可以用right来返回索引。

class Solution{
	public:
		int searchInsert(vector<int>& nums, int target){
		int left = 0;
		int right = nums.size();
		while(left < right){//这里和上面的没区别
		int mid = left + ((right - left) >> 1);
		if (nums[mid] == target)
			return mid;//return 不仅跳出了本次循环,也会结束这个函数
		else if(nums[mid] > target)
			right = mid; //没区别
		else
			left = mid + 1;//没区别
		}
		return right;//这里right和left没区别,因为左=右的时候while结束
		}
}

我犯懒第二种情况就不写了,循环结束条件和上面的没区别,return left或return right+1都可以。
其实,return left对于两种情况都是对的,right的情况会发生变化,我的建议是当场画一个图会解决很多的问题。

除此之外,O(n)的暴力循环方法也是可以的。

for (int i = 0; i < nums.size(); i++){
	if (nums[i] >= target)
		return i;//一旦发现大于或者等于这个target的元素之后,插进来,这个新元素的位置就是之前老元素的位置
}
return nums.size();

题目:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:你可以设计并实现时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法解决此问题吗?

这道题的关键是我们需要确定左右边界,那么如何确定呢?做了这几道题之后,我们可以发现对于二分法来说,while循环的条件是不变得就那么两种。变化的是循环体内部的代码。

class Solution{
	public:
		vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        // 情况一
        if (leftBorder == -2 || rightBorder == -2) return {-1, -1}; //lb=-2时,target在数组的右侧;rb=-2时,target在数组的左侧
        // 情况三
        if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1}; 
        // 情况二
        return {-1, -1};//数组中没有想要找到的元素
    }
	private:
		int getRightBorder(vector<int>& nums, int target){
			int left = 0;
			int rightBorder = -2;
			int right = nums.size() - 1;
			while (left <= right){
				int mid = left + ((right - lef) >> 1);
				if (target < nums[mid])
					right = mid - 1;//如果target比所有的元素都小,那么right会变成-1此时right<left跳出循环,rightBorder还是-2
				else { //注意这里,我在寻找有边界,只要target目标值大于等于mid,我就要在右边的区间持续的寻找left
					left = mid + 1;
					rightBorder = left; //这里的rightBorder事实上是最后一个target的后面元素的索引
				}					
			}
			return rightBorder;
		}
		int getLeftBorder(vector<int>& nums, int target){
			int left = 0;
			int lefttBorder = -2;
			int right = nums.size() - 1;
			while (left <= right){
				int mid = left + ((right - lef) >> 1);
				if (target > nums[mid])
					left = mid + 1;
				else{//小于等于的时候都遍历,直到找到一个不是target的元素。
				right = mid - 1;
				leftBorder = right;//最左边target的上一个元素的索引
				}
			}
			return leftBorder;
		}
};

找左右边界的时候跳出循环的情况是right<left。
为什么要right - left >1呢?如果单单left < right的话是不对的,因为如果假如1,2,4,4,5寻找3.此时LB=1,RB=2,按照这个条件是错误的。换句话来讲,我最后的答案需要是,lb+1和rb-1,所以最起码之间的距离大于1才满足条件。
为什么要设LB和RB初始为-2,-1不可以吗?假如数组只有一个元素,target恰好也正是这唯一的元素,此时lb=-1,rb=1,假如你初始设lb为-1那么这个时候会认为是数组中不存在这个元素,所以设置为-2.

二刷感悟:
这里的rightBorder和leftBorder到底是什么?其实就是夹住target的两侧,比如说1,2,3,找2,那么leftBorder和rightBorder就是夹住2的1和3,索引0和2.
还是回到初始化为-2的问题。假如数组是4,5,5,6,7,7,我们找3,那么此时leftBorder和rightBorder都是初始值;如果,我们找4,那么此时leftBorder是-1,rightBorder是1,因此就不能把左右边界初始化为-1,因为这样的话就分不清这两种情况了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值