关于二分查找的具体实现和原理解释,在之前的一篇文章里有专门提到,详解请见二分查找
这篇文章,我们主要是利用二分查找的基本原理来解决一些问题~
一、在一个有序数组里查找元素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];
}
};