原题
统计一个数字在排序数组中出现的次数。
Reference Answer
思路分析
- 蠢一点的方法,直接遍历统计:
# -*- coding:utf-8 -*-
class Solution:
def GetNumberOfK(self, data, k):
# write code here
count = 0
for x in data:
if x == k:
count += 1
return count
时空复杂度均为 O(n),这道题明显不是想走顺序遍历,不然太没意思!
- 省事一些的方法
直接调用python count方法,最省事,直接输出对应元素的次数。
# -*- coding:utf-8 -*-
class Solution:
def GetNumberOfK(self, data, k):
# write code here
return data.count(k)
这个方法太取巧,不是面试官想考察的意思,下面用正规解法解决下这个问题。
优化版:
既然是已经排序好的数组,那么第一个想到的就是二分查找法。
做法就是使用二分法找到数字在数组中出现的第一个位置,再利用二分法找到数字在数组中出现的第二个位置。时间复杂度为O(nlogn + nlogn),最终的时间复杂度为O(nlogn)。
举个例子,找到数字k在数组data中出现的次数。
数组data中,数字k出现的第一个位置:
我们对数组data进行二分,如果数组中间的数字小于k,说明k应该出现在中间位置的右边;如果数组中间的数字大于k,说明k应该出现在中间位置的左边;如果数组中间的数字等于k,并且中间位置的前一个数字不等于k,说明这个中间数字就是数字k出现的第一个位置。
同理,数字k出现的最后一个位置,也是这样找的。但是判断少有不同。我们使用两个函数分别获得他们。
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
if (data.empty()){
return 0;
}
int length = data.size()-1;
int left = getFirstK(data, k, 0, length);
int right = getLastK(data, k, 0, length);
if ((left != -1) && (right != -1)){
return right - left + 1;
}
return 0;
}
int getFirstK(vector<int> data, int k, int begin, int end){
if(begin > end){
return -1;
}
int length = data.size()-1;
int mid = (begin + end) >> 1;
while (begin <= end){
if(data[mid] == k){
if((mid > 0 && data[mid-1] != k) || (mid == 0)){
return mid;
}
else{
end = mid-1;
}
}
else if(data[mid] > k){
end = mid - 1;
}
else{
begin = mid + 1;
}
mid = (begin + end) >> 1;
}
return -1;
}
int getLastK(vector<int> data, int k, int begin, int end){
if(begin > end){
return -1;
}
int length = data.size()-1;
int mid = (begin + end) >> 1;
while(begin <= end){
if(data[mid] == k){
if((mid < length && data[mid+1] != k) || mid == length){
return mid;
}
else{
begin = mid + 1;
}
}
else if(data[mid] > k){
end = mid - 1;
}
else{
begin = mid + 1;
}
mid = (begin + end) >> 1;
}
return -1;
}
};
补充 Python 版:
# -*- coding:utf-8 -*-
class Solution:
def GetNumberOfK(self, data, k):
# write code here
if not data:
return 0
left,right = 0, len(data) - 1
while left <= right:
mid = int((left + right) // 2)
if data[mid] > k:
right = mid - 1
elif data[mid] < k:
left = mid + 1
else:
left = mid - 1
right = mid + 1
while left >= 0 and data[left] == k:
left -= 1
while right <= len(data) - 1 and data[right] == k:
right += 1
return right - left - 1
return 0
Note:
- 这道题明显采用了分治法的思路,首先分为三种情况,k 在 data 左半边, k 在 data 右半边,k 包含在中间;再针对中间分情况讨论得到 k 的开始、结束点,最后得到答案,思路很值得学习反思!!