【算法】——有关二分查找的例题总结

关于二分查找的具体实现和原理解释,在之前的一篇文章里有专门提到,详解请见二分查找
这篇文章,我们主要是利用二分查找的基本原理来解决一些问题~

一、在一个有序数组里查找元素val,若找到了返回元素下标,没有找到,返回该元素在数组中应该插入的位置
如下图:
在这里插入图片描述
分析:
首先,我们要返回两个值,一个是元素下标,一个是找没找到val值的布尔值,所以我们要定义一个结构体,来表示这两个值,如下:

typedef struct
{
	int index;
	bool tag;//找到了为ture,flase
}Result;

其次,我们再来探讨是返回left、right还是mid下标所指的值,以val =40为例子,查找过程如下:
在这里插入图片描述
由上图可知,当查找到第4趟的时候left==right,但是45>40,所以right = mid-1;才退出二分查找的过程。而最终left所指的位置才是40 应该插入的位置。
所以当我们在数组中找不到val值时,应该插入的元素下标为left所指的位置。
写出代码如下:

Result Find(int* ar, int left, int right, int val)
{
	Result res = { -1,false };
	while(left <= right)
	{   
		int mid = (right - left + 1) / 2 + left;
		if (val < ar[mid])
		{
			right = mid - 1;
		}
		else if (val > ar[mid])
		{
			left = mid + 1;
		}
		else
		{
			res.index = mid;
			res.tag = true;
			break;
		}
	}
	if (!res.tag)
	{
		res.index = left;
	}
	return res;
}

Result FindValue(int* ar, int n, int val)
{
	Result res = { -1,false };
	if (ar != NULL && n > 0)
	{
		res = Find(ar, 0, n - 1, val);
	}
	return res;
}

还有一种递归的方式实现,代码如下:

Result Find2(int* ar, int left, int right, int val)
{
	Result res = { -1,false };
	int mid = (right - left + 1) / 2 + left;
	if (left <= right)
	{
		if (val < ar[mid])
		{
			res = Find2(ar, left, mid - 1, val);
		}
		else if (val > ar[mid])
		{
			res = Find2(ar, mid + 1, right, val);
		}
		else
		{
			res.index = mid;
			res.tag = true;
		}
	}
	if (!res.tag && res.index == -1)//说明没有赋值,所以给他赋值
	{
		res.index = mid;
	}
	return res;
}

但是递归的过程会相对于复杂一点,不能像循环那样来思考,他会有一个回溯的过程,最后找不到时,应该返回的是mid所指的下标

主函数为:

int main()
{
	int ar[] = { 12,23,34,45,56,67,78,89,95,100 };
	int n = sizeof(ar) / sizeof(ar[0]);
	int val;
	while (cin >> val, val != -1)
	{
		Result res = FindValue(ar, n, val);
		cout << res.index << " " << res.tag << endl;
	}
	return 0;
}

二、贪吃的小Q
小Q的父母要出差N天,走之前给小Q留下了M块巧克力。
小Q决定每天吃的巧克力数量不少于前一天吃的一半,
但是他又不想在父母回来之前的某一天没有巧克力吃,
请问他第一天最多能吃多少块巧克力。
例如输入3,7
输出4

分析:还是采用二分查找的思维,定义一个mid值作为第一天所吃的巧克力数,以后每天的巧克力值按照(mid-1)/2的方式递减。最后用一个sum函数计算总共n天所吃的巧克力数。
如下图:
在这里插入图片描述
注意!
1、不要拿一个确定的sum值和m精确比较,因为还是可以吃多于一半的数。只用找到第一天吃的就行,后面吃的没有确切的,有多种吃法
2、因为要求最多第一天可以吃多少,所以返回的是right值
代码实现如下:

int Sum(int n, int mid)
{
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += mid;
		//mid = (mid/ 2) + 1; 错误
		mid = (mid + 1) / 2;
	}
	return sum;
}
int Get_Max(int n, int m)
{
	int left = 1, right = m;
	while (left <= right)
	{
		int mid = (right + left) / 2;
		int sum = Sum(n, mid);
		bool tag = sum > m;
		if (tag)
		{
			right = mid - 1;
		}
		else
		{
			left = mid + 1;
		}
	}
	return right;
}
int main()
{
	int nday, m;
	while (cin >> nday >> m, nday != -1 && m > 0)
	{
		int maxm = Get_Max(nday, m);
		cout << maxm << endl;
	}
}

三、反转数组
题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

分析:
采用二分法解答这个问题,
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边
还是右边,这时只好一个一个试 ,
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左
边。因为右边必然都是递增的。
high = mid

代码实现:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
		if(rotateArray.empty())
			return 0;
		int left = 0 ;
		int right = rotateArray.size() - 1;
		while(left < right){
			//确认子数组是否是类似1,1,2,4,5,..,7的非递减数组
			if(rotateArray[left] < rotateArray[right])
				return rotateArray[left];
			int mid = left + (right - left) / 2;

			//如果左半数组为有序数组
			if(rotateArray[left] < rotateArray[mid])
				left = mid + 1;

			//如果右半数组为有序数组
			else if(rotateArray[mid] < rotateArray[right])
				right = mid;

			//否则,rotateArray[mid] = rotateArray[right] = rotateArray[left]
			else{
				++left;
			}
		}
		return rotateArray[left];
    }
};
发布了62 篇原创文章 · 获赞 7 · 访问量 2559

猜你喜欢

转载自blog.csdn.net/qq_43412060/article/details/104713877