如果我们熟悉二分查找,我们就知道二分查找有一个重要的基础,就是需要有序的顺序表。这里有两点,一个是有序,另一个是顺序表。一般来说,只要是查找的题目,和这两者挂上钩,基本就是二分查找无疑了。因为二分查找过于重要,建议大家自己要能写出代码。
题目描述
统计一个数字在一个排序数组中出现的次数。
题目分析
如果说从前往后扫描一个数组,统计这个数字在数组中出现的次数,显然复杂度是的,完全用不到排序数组这个特点。我们要想用上这个特点,在一个排序数组中一个数字出现多次的话,显然这写数字是挤在一处的,我们只需要找到第一次出现的位置,然后找到最后一次出现的位置,显然就可以轻松得到这个数字出现的次数。那就是在有序数组中找第一次出现的位置和最后一次出现的位置。不用说了,必然二分查找。
但是我们知道普通的二分查找是找一个数字出现的位置,如果这个数字出现多次,二分查找只能返回其中一个的位置。而这个时候,我们就需要对代码进行修改,修改为可以保证只查找到该数的第一个数字或者最后一个数字。这个时候就完成了任务。但是这样可能就要写两个函数,一个找第一次出现的位置,一个找最后一次出现的位置。
我们可以对这个问题稍微做一做变通,我们在学习有序表的插入时候,可以通过二分查找找到插入的位置,我们也可以对代码进行修改,保证相同的数插入在最后面或者最前面。我们以插在最前面为例。插在最前面的位置显然就是这个数的起始位置(如果存在),我们可以通过这个函数来转换一下找到这个数的最后位置。如果我们的数组是整数类型,那么显然这个数加1的数的插入位置就是下一个数的起始位置,这个数的起始位置应该前面就是我们要找的数。两个索引之差就是要找的数的个数。如果这个数不存在,显然这个数的插入位置和下一个数的插入位置就是同一个位置,这个时候两个索引之差为0,也满足我们所述的关系,分析完毕。
C++代码
class Solution {
public:
int GetNumberOfK(vector<int> data, int k) {
return BinarySearch(data, k +0.5) - BinarySearch(data, k-0.5);
}
int BinarySearch(vector<int> data, int k)
{
int begin = 0, end = data.size()-1, mid;
while (end >= begin)
{
mid = (begin + end) >> 1;
if (data[mid] == k)
{
if ((mid > 0 &&data[mid - 1] != k) || (mid == 0))
return mid;
}
if (data[mid] >= k)
end = mid - 1;
else
begin = mid + 1;
}
return begin;
}
};
事实上这个问题可以进一步拓展,如果数组是整数,我们需要统计a的个数,我们可以在两边找紧邻着这个数但是数组中不存在的数(显然a-0.5和a+0.5就满足条件)的插入位置,这两个位置的间隔就是a的个数。
再次强调一遍,只要是有序数组的查找,必然和二分查找分不开了。要注意会转换问题。这里再需要补充的一点是,二分查找是基于分治的,如果一个数组即便不是有序的,但是可以划分成子问题,而且子问题的性质和原来问题的性质是一样的,并且这个划分可以根据特性排除掉一个子问题,这个时候就满足二分法的需求了。比如下面这个问题:
二分法示例补充
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
来源:CSDN
作者:August-us
链接:https://blog.csdn.net/m0_38065572/article/details/104522499