二分查找的特性及应用

假如想象 提交于 2020-03-02 11:31:42

  如果我们熟悉二分查找,我们就知道二分查找有一个重要的基础,就是需要有序的顺序表。这里有两点,一个是有序,另一个是顺序表。一般来说,只要是查找的题目,和这两者挂上钩,基本就是二分查找无疑了。因为二分查找过于重要,建议大家自己要能写出代码。

题目描述

  统计一个数字在一个排序数组中出现的次数。

题目分析

  如果说从前往后扫描一个数组,统计这个数字在数组中出现的次数,显然复杂度是O(n)O(n)的,完全用不到排序数组这个特点。我们要想用上这个特点,在一个排序数组中一个数字出现多次的话,显然这写数字是挤在一处的,我们只需要找到第一次出现的位置,然后找到最后一次出现的位置,显然就可以轻松得到这个数字出现的次数。那就是在有序数组中找第一次出现的位置和最后一次出现的位置。不用说了,必然二分查找。
  但是我们知道普通的二分查找是找一个数字出现的位置,如果这个数字出现多次,二分查找只能返回其中一个的位置。而这个时候,我们就需要对代码进行修改,修改为可以保证只查找到该数的第一个数字或者最后一个数字。这个时候就完成了任务。但是这样可能就要写两个函数,一个找第一次出现的位置,一个找最后一次出现的位置。
  我们可以对这个问题稍微做一做变通,我们在学习有序表的插入时候,可以通过二分查找找到插入的位置,我们也可以对代码进行修改,保证相同的数插入在最后面或者最前面。我们以插在最前面为例。插在最前面的位置显然就是这个数的起始位置(如果存在),我们可以通过这个函数来转换一下找到这个数的最后位置。如果我们的数组是整数类型,那么显然这个数加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。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!