【二分查找】在排序数组中查找数字

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/83187284

排序数组里很多二分查找的题目,不能把排序这个性质浪费了。

面试题53-1:数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。例如输入排序数组{1, 2, 3, 3, 3, 3, 4, 5}和数字3,由于3在这个数组中出现了4次,因此输出4。

二分查找第一个k和最后一个k,计算它们的距离再+1。

#include<bits/stdc++.h>
using namespace std;

// 参数:
//        data:        一个升序有序的整数数组
//        length:      数组的长度
//        k:           要寻找的数字 
//        start:       (子)数组起始位置下标 
//        end:         (子)数组结束位置下标 

//找到数组中第一个k的下标,如果数组中不存在k,返回-1
int GetFirstK(const int* data, int length, int k, int start, int end) {
	if(start > end)
		return -1;

	int middleIndex = (start + end) / 2;//二分查找,分割点 
	int middleData = data[middleIndex];//分割点处的值 

	if(middleData == k) {//分割点处值是k
		//分割点不是0,那么它前面一个数字如果不是k,它就是第一个k 
		//分割点是0即一定是第一个k 
		if((middleIndex > 0 && data[middleIndex - 1] != k)
		        || middleIndex == 0)
			return middleIndex;//找到了第一个k 
		else//它不是第一个k,第一个k一定在它左边 
			end  = middleIndex - 1;
	} else if(middleData > k)//分割点处值比k大 
		end = middleIndex - 1;//升序数组,k海如果存在一定在左边 
	else//分割点处比k小 
		start = middleIndex + 1;//升序数组,k海如果存在一定在右边 
	
	//运行至此说明还没找到第一个k,递归调用二分查找 
	return GetFirstK(data, length, k, start, end); 
}

//找到数组中最后一个k的下标,如果数组中不存在k,返回-1
//GetLastK和GetFirstK的思路一样 
int GetLastK(const int* data, int length, int k, int start, int end) {
	if(start > end)
		return -1;

	int middleIndex = (start + end) / 2;
	int middleData = data[middleIndex];

	if(middleData == k) {//分割点处值是k
		//分割点不是length-1(最后一个点),那么它后面的点要不是k它就是最后一个
		//分割点是length-1,后面已经没了,它一定是最后一个k 
		if((middleIndex < length - 1 && data[middleIndex + 1] != k)
		        || middleIndex == length - 1)
			return middleIndex;
		else
			start  = middleIndex + 1;
	} else if(middleData < k)
		start = middleIndex + 1;
	else
		end = middleIndex - 1;

	return GetLastK(data, length, k, start, end);
}

//在长为length的排序data数组中计算k出现的次数 
int GetNumberOfK(const int* data, int length, int k) {
	int number = 0;//找不到就是0个 

	if(data != nullptr && length > 0) {//输入合法性检查 
		//找第一个k的下标和最后一个k的下标 
		int first = GetFirstK(data, length, k, 0, length - 1);
		int last = GetLastK(data, length, k, 0, length - 1);

		//大于-1即证实k的存在性,实际上这两个条件只要有一个满足另一个一定满足
		//因为只要"第一个k"存在,则"最后一个k"一定存在 
		if(first > -1 && last > -1)
			number = last - first + 1;//两个k之间全是k,因为数组是排序数组 
	}

	return number;
}

int main() {
	int data[] = {1, 2, 3, 3, 3, 3, 4, 5};
	cout<<GetNumberOfK(data,sizeof(data)/sizeof(int),3)<<endl;//4
	return 0;
}

面试题53-2:0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

比如n=8,0~7缺了4,本来应该是:
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 0,1,2,3,4,5,6,7
现在变成:
0 , 1 , 2 , 3 , 5 , 6 , 7 0,1,2,3,5,6,7
可以看到前面的那部分下标和数字相等,后面的部分数字比下标大了1,第一个数字比下标大的数字,其所在位置就是缺的那个数字本来应该在的位置。

#include<bits/stdc++.h>
using namespace std;

//数组长为length的升序数字数组numbers,返回缺失的数字
//这里应该注意length就是题目中的n-1
//因为n个数字缺了1个只剩n-1个,实际读入就读了n-1个而不是读了n个 
int GetMissingNumber(const int* numbers, int length) {
	//输入合法性检查 
	if(numbers == nullptr || length <= 0)
		return -1;

	int left = 0;//二分查找左位置 
	int right = length - 1;//二分查找右位置 
	while(left <= right) {//没找到时左>右 
		int middle = (right + left) >> 1;//二分中点 
		if(numbers[middle] != middle) {//如果和下标不同
			//下标是0,就缺0
			//下标不是0,看左边一个数是不是和它的下标相等 
			if(middle == 0 || numbers[middle - 1] == middle - 1)
				return middle;//如果是,当前下标就是缺的那个数 
			right = middle - 1;//否则,往左找 
		} else//中点和下标相同 
			left = middle + 1;//往右找 
	}

	//特别注意,当缺的那个数就是n-1的时候,上面的循环找不到
	//一直往右找,最后找到left=length超过right=length-1结束循环 
	if(left == length)
		//特别注意length就是n-1,length-1可就是n-2了 
		return length; 

	//无效的输入,比如数组不是按要求排序的
	//或者有数字不在0到n-1范围之内
	return -1;
}


int main() {
	int numbers[]={0,1,2,3,5,6,7};
	cout<<GetMissingNumber(numbers,7)<<endl;//4
	return 0;
}

面试题53-3:数组中数值和下标相等的元素

假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数找出数组中任意一个数值等于其下标的元素。例如,在数组{-3, -1, 1, 3, 5}中,数字3和它的下标相等。

左边的数字都比下标小或和下标相等,右边的数字都比下标大或和下标相等。因为是找任意一个,所以这两个条件只要设置成"小"和"大"就行了。

#include<bits/stdc++.h>
using namespace std;

//寻找长度为length的升序数组numbers中某个和下标相等的元素
int GetNumberSameAsIndex(const int* numbers, int length) {
	if(numbers == nullptr || length <= 0)//输入合法性
		return -1;
	//二分左右点
	int left = 0;
	int right = length - 1;
	//查找
	while(left <= right) {
		//二分划分点,其实就是(right+left)>>2
		//我觉得下面作者这种写法的优势就是能避免上面的right+left越界
		int middle = left + ((right - left) >> 1);
		//中点和下标相等
		if(numbers[middle] == middle)
			return middle;//找到
		//中点值比下标大
		if(numbers[middle] > middle)
			right = middle - 1;//往左找
		else//比下标小
			left = middle + 1;//往右找
	}
	return -1;//没找到
}


int main() {
	int numbers[] = { -3, -1, 1, 3, 5 };
	cout<<GetNumberSameAsIndex(numbers,sizeof(numbers)/sizeof(int))<<endl;//3
	return 0;
}

思考,如果是要找第一个(最后一个)和下标相等的元素呢?就和前面两个题比较像了,判断条件里要同时判断一下前面(后面)的一个元素,以及是不是第一个(最后一个)元素。

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/83187284