题意
统计一个数字在排序数组中出现的次数。
解法1—暴力循环
class Solution
{
public:
int search(vector<int>& nums, int target)
{
int count=0;
for(auto& num:nums)
{
if(num==target)
count++;
}
return count;
}
};
解法2—哈希表
class Solution
{
public:
int search(vector<int>& nums, int target)
{
unordered_map<int,int> umap;
for(auto& num:nums)
{
umap[num]++;
}
return umap[target];
}
};
解法3—二分法
由题目可知,nums是一个非递减数组。排序数组中的搜索问题,首先想到 二分法 解决。
排序数组 nums 中的 所有target数字 形成一个窗口,记窗口的 左 / 右边界 索引分别为 left 和 right ,分别对应窗口左边 / 右边的首个元素。
本题要求统计数字 target 的出现次数,可转化为:使用二分法分别找到 左边界 left 和 右边界 right ,易得数字 target 的数量为 right - left - 1。
这样的话呢,我们会把算法流程分为两部分,第一部分是查找左边界,第二部分是查找右边界,有没有办法将二者统一起来呢?
假设定义我们的二分查找函数是 f,当它的输入参数是x的时候,我们令 f(x)返回结果为x在有序数组nums中插入的位置,如果 f(x) 返回的结果是i的话,这就说明在nums的[0,i-1]闭区间的范围内的所有元素统统是 小于等于x 的;而在nums的[i+1,n-1]闭区间内所有的数字统统是大于x的。
例如:
f(7) = 3。即target形成的窗口的第一个位置。
f(8) = 5。即target形成的窗口的最后一个位置的下一个位置。
这样,直接返回 f(8)-f(7)就可以得到最终的答案了。
f函数算法解析:
- 初始化: 左边界 L = 0,右边界 R = len(nums) - 1。
- 循环二分:当L大于R时跳出循环
- 计算中点 m = L+(R-L)/2 (向下取整);
-
若 nums[m] ≤ target ,中点值比target小或者等于它,target插入位置肯定在[m+1,R] 闭区间了,因此执行 L = m + 1;
-
若 nums[m] > target,中点值比target大,target插入位置肯定在中点之前了,即[L,m-1]闭区间 ,因此执行 R = m - 1;
- 返回值: 返回最终的答案L。
C++实现
class Solution
{
public:
int f(vector<int>& nums,int x)
{
//寻找x的右边界
//本质上来说就是查找数字x在Nums中的插入位置
//若数组中存在值相同的元素,则插入位置为这些元素的右边
int L=0,R=nums.size()-1;
//当左右边界(L和R)指向同一个位置的时候,并没有办法判断 m 位置处的元素是≤x还是>x
//因为我们最终返回的是L,而如果说 nums[m]≤x,L是需要在原有基础上加一的
//否则,若nums[m]>x,L不会被改变,应该改变R,使得R<L,跳出循环。
//所以即使 L等于R的时候,依然要判断 m处位置的元素与x的大小关系。
while (L<=R)
{
int m = L+(R-L)/2;
if(nums[m]<=x)
L=m+1;
else
R=m-1;
}
return L;
}
int search(vector<int>& nums, int target)
{
//去找 (target-1) 的右边界,实际上就是查找target的左边界所在位置
//主要是找位置,而不是找数字存不存在,target-1的目标是找到离target的左窗口最近的那个位置点。
return f(nums,target)-f(nums,target-1);
}
};