写在前面
简直offer:数字在排序数组中出现的次数
题目描述
统计一个数字在排序数组中出现的次数。
解法
暴力解:遍历数组记录出现个数。时间复杂度为o(n)
进阶解法:
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(!data.size()) return 0;
int res = 0;
//采用二分搜索的方式进行搜索
int l = 0,r = data.size()-1;
while(l<=r) {//当只有一个元素的时候,l<r是进不了循环的。这里的判断条件需要带入特殊的值进行判断。
int mid = l + (r-l)/2;
if(data[mid]>k) {
r = mid-1;
}
else if(data[mid]<k) {
l = mid+1;
}
else {
//如果二分找到k就判断其周围有没有
for(int i=mid;i<=r;++i) {
if(data[i]==k) ++res;
else break;
}
for(int i=mid-1;i>=l;--i) {
if(data[i]==k) ++res;
else break;
}
break;
}
}
return res;
}
};
假设数组的长度为n,k元素出现的次数为m次。
算法的时间复杂度为o(logn+m)。如果k出现的次数为n算法的复杂度将退化为o(n)。
有没有更好的解法?
分析: 上面的算法的复杂度来源于当使用二分找到第一个k的时候,还需要在k的两边遍历去寻找重复的k。如何省去这个遍历的过程是改进的关键。
思路:合理利用二分搜索。使用找上界和找下界的方式一次性找到k的上下界这时只需要相减就能够得到k的个数。找上下界的时间复杂度都是o(logn)。所以整个算法的复杂度为o(logn)。
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if(!data.size()) return 0;
int l = getleft(data,k);
int r = getright(data,k);
if(l==-1&&r==-1)
return 0;
return r - l + 1;
}
//找到第一个k的位置
int getleft(vector<int>& data,int k) {
int l = 0, r = data.size()-1;
while(l<=r) {
int mid = l + (r-l)/2;
if(data[mid]>k) {
r = mid-1;
}
else if(data[mid]<k)
l = mid+1;
else {
if(mid==0||mid-1>=0&&data[mid-1]!=k)
return mid;
else r = mid-1;
}
}
return -1;
}
//找到最后一个k的位置
int getright(vector<int>& data,int k) {
int l = 0, r = data.size()-1;
while(l<=r) {
int mid = l + (r-l)/2;
if(data[mid]<k) {
l = mid+1;
}
else if(data[mid]>k) {
r = mid-1;
}
else {
if(mid==data.size()-1||mid+1<data.size()&&data[mid+1]!=k)
return mid;
else l = mid +1;
}
}
return -1;
}
};