排序数组里很多二分查找的题目,不能把排序这个性质浪费了。
面试题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,4,5,6,7
现在变成:
0
,
1
,
2
,
3
,
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;
}
思考,如果是要找第一个(最后一个)和下标相等的元素呢?就和前面两个题比较像了,判断条件里要同时判断一下前面(后面)的一个元素,以及是不是第一个(最后一个)元素。