剑指offer:Python 数组中出现次数超过一半的数字 图解 详细过程

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路和Python实现

思路一

直接用字典来存 (哈希表) 时间复杂度为O(n) ,但是空间复杂度也为O(n)

class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        count_dic = {}
        num_len = len(numbers)
        for item in numbers:
            if item in count_dic:
                count_dic[item] += 1
            else:
                count_dic[item] = 1
            if count_dic[item] > (num_len >> 1):
                return item
        return 0
class Solution:
    def MoreThanHalfNum_Solution(self, numbers): 
        from collections import defaultdict
        cnt = defaultdict(lambda: 0)
        n = len(numbers) >> 1
        for i in numbers:
            cnt[i] += 1
            if cnt[i] > n:
                return i
        return 0


obj = Solution()
print(obj.MoreThanHalfNum_Solution([1, 2, 3, 2, 4, 2, 5, 2, 3])) # 0
思路二

假设有这个数字,那么它的数量一定比其它所有数字之和还要多,找出这个数量最多的数。

  • 为了能保持时间复杂度为O(n),并且达到空间复杂度为O(1),我们肯定不能使用字典和嵌套循环,循环可以用很多,但是要并行的,变量可以随便定义,只要达不到n个都行,所以该怎么做呢?
  • 具体做法可以这样:遍历列表,两两相同的数不抵消,总数记录+1个,两两不同的数相互抵消,总数记录-1个,如果最终记录为0,怎没有返回0,不为0,则可能存在,找到当前这个last,需要进入一步判断(下面详细讨论)。
  • 具体过程是:用一个last 表示剩下的数,last_count 表示当前剩下的这个数的个数,如果last_count=0表示,没有剩下的数了(都是两两不同,抵消了;开头就可以看做是上一个过程没有剩下数字,所以可以直接把第一数加入进来),这时接着遍历到下一个数时,就把这个数加入进来,然后和下一次遍历出来的数比较,相同,last_count+=1,不同 last_count-=1,直到last_count=0等于0,这段时间作为前驱比较的数,都是最初last_count=0时加入进来的那个数;如此进行下去,如果最终last_count还是等于0,就说明没有符合题意的数,直接return0 如下图的列表↓↓↓
    在这里插入图片描述
  • 如果最终last_count 不为0(如下图↓↓↓)则说明可能存在这样的数,就是要找到这个last,接着要判断这个last(最后的剩下的数)是否比列表的一半长度要长即可
    在这里插入图片描述
  • 接下来就重新遍历一遍列表,统计 等于last这个数的个数,然后判断是否 大于 列表的一半长度,如果大于就返回这个值,没有return 0
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        last = 0
        last_count = 0

        for item in numbers:
            if last_count == 0:
                last = item
                last_count = 1
            else:
                if item == last:
                    last_count += 1
                else:
                    last_count -= 1 # 上面的步骤就是找last
        if last_count == 0:
            return 0
        else: # last个数不为0 
            last_count = 0
            for item in numbers:
                if item == last:
                    last_count += 1
            if last_count > (len(numbers) >> 1):
                return last
        return 0


obj = Solution()
print(obj.MoreThanHalfNum_Solution([1, 2, 2, 2, 4])) # 2
思路三

基于快排

  • 如果数组中有一个数字出现的次数超过了数组长度的一半,将这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组一半的数字(快排最直观体现),也就是说,这个数字就是统计学上的中位数,即长度为n的数组中第n/2的数字。
  • 在快速排序算法中,我们现在数组中随机选择一个数字,然后调整数组中数字的顺序,使得比选中的数字小的数字都排在它的左边,比选中的数字大的数字都排在它的右边。如果这个选中的数字的下标刚好是n/2,那么这个数字就是数组的中位数。如果它的下标大于n/2,那么中位数应该位于它的左边,我们可以接着在它的左边部分的数组中查找。如果它的下标小于n/2,那么中位数应该位于它的右边,我们可以接着在它的右边部分的数组中查找。
  • 排序千万别急着 sort(),sort()采用的是 Timsort,这是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,TimSort 是 Python 中 list.sort 的默认实现,sort()函数的时间复杂度最差是O(nlogn),快排的速率平均可以达到O(nlogn) ↓↓↓
    在这里插入图片描述

使用 sort.() 来操作,和先用快排的后序操作是一样的

class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        length = len(numbers)
        if length == 0:
            return 0
        elif length == 1:
            return numbers[0]
        else:
            numbers.sort()
            num = numbers[int(length >> 1)]
            if numbers.count(num) > (len(numbers) >> 1):
                return num
            return 0


obj = Solution()
print(obj.MoreThanHalfNum_Solution([1, 2, 3, 2, 4, 2, 5, 2, 3])) # 0

先 基于快排,然后判断

# 有空补充
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值