算法第六记-二分查找

为了准备春招,自己重新总结了一下二分查找的各种形式,以及几个二分查找的面试题:

1.普通的二分查找

int binary_find(int arr[], int length,int value)//普通的二分查找
{
   if(!arr||length<=0)
      return -1;
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] == value)
			return mid;
		else if (arr[mid] > value)
			high = mid - 1;
		else
			low = mid + 1;
	}
	return -1;
}

2.找到第一个值等于值的下标

int binary_find_1(int arr[], int length,int value)
{//找到第一个等于value的元素下标
    if(!arr||length<=0)
      return -1;
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] > value)
		{
			high = mid - 1;
		}else if(arr[mid<value]){
			low = mid + 1;
		}
		else
		{
			if (mid == 0 || arr[mid - 1] != value)
				return mid;
			else
				high = mid - 1;
		}
	}
	return -1;
}

3.查找最后一个值等于值的下标

int binary_find_2(int arr[], int length, int value)
{//找到最后一个等于value的元素下标
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid]<value)
		{
				low = mid + 1;
		}
		else if(arr[mid]>value)
			high = mid - 1;
		else
		{
			if (mid == length - 1 || arr[mid + 1] != value)
				return mid;
			else
				low = mid + 1;
		}
	}
	return -1;
}

4.查找第一个值大于等于值的下标(相当于找右边界)1,3,5,7,8,9查找6应该返回第一个大于6的元素下标7

int binary_find_4(int arr[], int length, int value)
{//找到第一个大于等于value的元素下标
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid] >=value)
		{
//如果此时mid已经是第一个元素了,那我们直接返回或者如果mid-1这个位置的元素小于value我们也返回
//因为我们的目的是找第一个大于等于value的下标,而此时arr[mid-1]已经小于value了
			if (mid == 0 || arr[mid - 1] < value)
				return mid;
			else
				high = mid - 1;
 //如果arr[mid-1]依然大于value,则我们的右边界应改为mid-1
		}
		else
			low = mid + 1;
	}
	return -1;
}

5.查找最后一个值小于等于值的下标(相当于找左边界)0,1,1,2,6,9查找3的话应该返回下标3

int binary_find_3(int arr[], int length, int value)
{//找到最后一个小于等于value的元素下标
	int low = 0, high = length - 1;
	while (low <= high)
	{
		int mid = low + ((high - low) >> 1);
		if (arr[mid]<= value)
		{
//如果此时的mid是末尾元素了,则直接返回或者mid+1这个位置的元素已经大于value我们也直接返回mid
//因为我们的目的是找最后一个小于等于value的下标,mid这个位置的元素是小于等于value我们已经知道
//那么如果mid+1这个位置的元素是大于value的话,我们就找到了那个左边界
			if (mid == length - 1 || arr[mid + 1] > value)
				return mid;
			else
				low = mid + 1;
          //如果此时的mid+1这个位置的元素依然是小于value,我们就继续从右边开始找
		}
		else
		{
			high = mid - 1;
		}
	}
	return -1;
}

6.求一个循环有序数组的最小值例如(5,1,2,3,4),5 

输出:1

int find_Min(vector<int> arr, int index1, int index2)
{
	int result = arr[index1];
	for (int i = index1 + 1; i <= index2; i++)
		if (result > arr[i])
			result = arr[i];
	return result;
}
int getMin(vector<int> arr, int n) {
	// write code here
	if (arr.empty())
		return -1;
	int index1 = 0;
	int index2 = n - 1;
	int mid = 0;//如果一开始循环条件就不满足,说明就是一个有序序列,所以最小值是下标0处
	while (arr[index1] >= arr[index2])
	{
      /*如果找到那个左边序列最大值与右边序列最小值的边界时,由于我们循环进来的条件是arr[index1]>=arr[index2],自然而然 最小值肯定是index*/
		if (index2 - index1 == 1)
		{
			mid = index2;
			break;
		}
		mid = index1 + ((index2 - index1) >> 1);
     //如果最左边的值和中间的值和最右边的值都相等的话,我们无法确定中间的那个值是属于前半段有序序 
     //列还是后半段有序序列,所以此时需要采用顺序查找
    //举例  1 0 1 1 1和 1 1 1 0 1 中间的1可以是左边序列也可以是右边序列
		if (arr[mid] == arr[index1] && arr[index1] == arr[index2])
			return find_Min(arr, index1, index2);
     //此时是在左边界比右边界大的前提下(循环条件就是如此),同时中间值比左边界还大,自然最小值不 //会出现在左半边,所以我们修改左边界 继续从右半边序列开始找
		if (arr[mid] >= arr[index1])
			index1 = mid;
		else if (arr[mid] <= arr[index2])
			index2 = mid;
     //此时是左边界比右边界大的前提下,同时中间值比左边界小,那么如果我们的中间值比右边界还小的话
    //说明左边有序序列的最大值与右边有序序列最小值的交界处此时应该是在mid左边,我们修改右边界,从 
    //mid左边的序列开始继续找
}
	return arr[mid];
}

7.有一个有序数组ARR,其中不含有重复元素,请找到满足ARR [I] ==我条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。

给定有序数组arr及它的大小n,请返回所求求值。

测试样例:

[-1,0,2,3],4  返回:2
int findPos(vector<int> arr, int n) {
        if (arr[0] > n - 1 || arr[n - 1] < 0)
		return -1;
	int left = 0;
	int right = n - 1;
	int mid;
	int res = -1;
	while (left <= right)
	{
     //如果当前值比下标还小,说明左半边不可能有arr[i]=i的出现(在没有重复值的前提下)
		int mid = (left + right) / 2;
		if (arr[mid]<mid)
		{
			left = mid+1;
		}
		else if (arr[mid] > mid)//如果当前值比下标大,右半边肯定也是延续的,所以我们看左半边
		{
			right = mid-1;
		}
		else
		{
			if(mid==0||arr[mid-1]!=mid-1)
                          return mid;
            else
                right=mid-1;
		}
	}
	return res;
    }

8.链表可以使用二分查找么?

答:可以使用,但是查找效率就达不到O(logn)时间了因为指定一个位置,链表必须从头开始走,不像数组那样具有随机定位的特性,所以每次我们找中间位置时必须从头走到那个位置,而不是像数组一样直接定位到中间。唯一的难点在于如何找到链表的中间点,这里就要运用到了一个额外的技巧——快慢指针。快慢指针常常用于判断链表中是否有环,简单来说就是有两个指针,快指针每次走两个节点,慢指针每次走一个节点,当快指针走到链表尽头时,慢指针才走到了链表的一半,此时就是我们想要的中间节点。那么链表使用二分查找效率会退化到多少呢?答案是O(N)!因为二分第一次要走的n / 2,第二次N / 4,第三次N / 8。根据等比数列前Ñ项和计算最终是线性级数。

9.计算一个数的平方根(使其精度在小数点后六位)

float sqrt(int x)
{
    double low=0;
    double mid=x/2;
    double high=x;
   while(abs(mid*mid-x)>0.000001)
    {
       if(mid*mid<x)
         low=mid;
       else
         high=mid;
      mid=(low+high)/2;
     } 
     return mid;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值