剑指offer 37. 数字在排序数组中出现的次数

原题

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

Reference Answer

思路分析

  1. 蠢一点的方法,直接遍历统计:
# -*- 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),这道题明显不是想走顺序遍历,不然太没意思!

  1. 省事一些的方法
    直接调用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 的开始、结束点,最后得到答案,思路很值得学习反思!!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值