面试高频--TOPK问题

Topk问题可以采用有序数组,无序数组,堆

347. 前 K 个高频元素 / 海量数据找最大的K个

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

虽然找TopK有很多方法,但是如果是海量数据,需要注意使用堆排序的时候,不要构建N大小的堆,构建K即可,然后维护K。

实现优先队列。常规做法是构建一个最小堆,然后依次弹出堆顶元素,再重新维护堆的性质,直到堆中只剩下K个节点,就是我们需要的TOPk元素了。此时维护的堆是n,时间复杂度是O(N),空间复杂度也是O(N), 并且会破坏堆的数据。

另外一种做法是维护一个k的堆,那么时间复杂度就是O(Nlogk)。维护一个k个节点的堆,然后把剩下的(n-k)个数依次插入堆中,最后得到一个k个节点的堆–就是我们需要的。

首先,使用最小堆,每次插入与堆顶比较,大于堆顶就对堆顶进行替换,然后下沉(logk)。

最大堆也可以,每次与最右边的叶子节点比较,每次大于叶子节点就进行替换,然后上浮。

建堆是从后往前,从左往右,注意可以从中间往前面,不需要从最后,因为都是叶子节点。
下面先写一个 堆排序 的基本框架: 主要包括建堆、堆的维护、
基本流程
1、建堆
2、把堆顶和堆底互换,后面的运算堆底不参与(==堆的数量减1)
3、将换到堆顶的新堆进行维护 heapify(nums, 0)
4、重复2、3直到堆的容量为0。------切记注意堆顶换到堆底并非实际的堆底,而是参与运算的堆的底部。

以下是建立最大堆的基本框架----如果是最小堆,则把if left < sizes and nums[left] > nums[largest] 改成 if left < sizes and nums[left] < nums[largest] 即可

		def Heapsort(nums):  # 堆排序
            size = len(nums)
            build_heap(size)
            while size > 0:
                # 其实这里没有必要重新建堆,可以看作前面都满足了堆的性质,只剩下根节点未满足。
                heapify(0, size)
                nums[0], nums[size - 1] = nums[size - 1], nums[0]  # 因为这种操作,所以切记不可以使用nums[-1]
                size -= 1
            return nums

        def build_heap(sizes):  # 建堆
            for i in range(sizes // 2, -1, -1):
                heapify(i, sizes)

        def heapify(i, sizes):  # 维护堆的性质
            left = 2 * i + 1
            right = 2 * i + 2
            largest = i
            if left < sizes and nums[left] > nums[largest]:
                largest, left = left, largest
            if right < sizes and nums[right] > nums[largest]:
                largest, right = right, largest

            if i != largest:
                nums[i], nums[largest] = nums[largest], nums[i]
                heapify(largest, sizes)
        
        sorted_nums = Heapsort(val_nums)

套进TOPK的基本框架–下面这种方法是建立了n大小的堆,然后最后取最后面的K个数就是需要的TopK.

class Solution(object):
    def topKFrequent(self, nums, k):
        ## 堆排序
        record = {
    
    }
        for i in nums:
            if i not in record:
                record[i] = 1
            else:
                record[i] += 1 # 记录出现的key以及对应的频率value
        
        record2 = {
    
    }
        val_nums = []
        for i in record:
            if record[i] not in record2:
                record2[record[i]] = [i]
            else:
                record2[record[i]].append(i)
            val_nums.append(record[i])  
  # 因为需要排序的是频率,所以key转变为频率,value是出现的数字的列表。

        nums = val_nums
 		#############堆排序###############
        def Heapsort(nums): 
            size = len(nums)
            build_heap(size)
            while size > 0:
                # 这里可以直接维护堆的性质,没有必要重新建堆
                # 可以看作后面都满足了堆的性质,只剩下根节点未满足。
                heapify(0, size)
                nums[0], nums[size - 1] = nums[size - 1], nums[0]  
       # 因为每次把满足条件的最大节点放在了最后,所以切记不可以使用nums[-1]
                size -= 1
            return nums

		########## 建堆 ############
        def build_heap(sizes):  
            for i in range(sizes // 2, -1, -1):
                heapify(i, sizes)
		###### 维护堆的性质 #########
        def heapify(i, sizes):  
            left = 2 * i + 1
            right = 2 * i + 2
            largest = i
            if left < sizes and nums[left] > nums[largest]:
                largest, left = left, largest
            if right < sizes and nums[right] > nums[largest]:
                largest, right = right, largest

            if i != largest:
                nums[i], nums[largest] = nums[largest], nums[i]
                heapify(largest, sizes)
        
        sorted_nums = Heapsort(val_nums)
        out = []
        for i in range(-1, -1-k, -1):
            out.append(record2[sorted_nums[i]][0])
            record2[sorted_nums[i]].pop(0)
        return out

下面是建立一个K的堆,然后将剩下的部分依次插入,只需要维护k大小的堆即可。节省了空间复杂度 K 和时间复杂度O(nlogk)
这里用的是最小堆,如果插入的元素大于堆顶,就和堆顶进行交换,然后维护堆的性质,直到插入完毕。----注意这里不需要用到堆排序,只需要建堆,之后,依次插入,如果插入成功就维护堆的性质,最后得到K个需要的数即可。–所以下面把堆排序删除。

class Solution(object):
    def topKFrequent(self, nums, k):
        ## 堆排序
        record = {
    
    }
        for i in nums:
            if i not in record:
                record[i] = 1
            else:
                record[i] += 1
        record2 = {
    
    }
        val_nums = []
        for i in record:
            if record[i] not in record2:
                record2[record[i]] = [i]
            else:
                record2[record[i]].append(i)
            val_nums.append(record[i])
        nums = val_nums
        
        def build_heap(nums, sizes):  # 建堆
            for i in range(sizes // 2, -1, -1):
                heapify(nums, i, sizes)
            return nums

        def heapify(nums, i, sizes):  # 维护堆的性质---最小堆
            left = 2 * i + 1
            right = 2 * i + 2
            largest = i
            if left < sizes and nums[left] < nums[largest]:
                largest, left = left, largest
            if right < sizes and nums[right] < nums[largest]:
                largest, right = right, largest

            if i != largest:
                nums[i], nums[largest] = nums[largest], nums[i]
                heapify(nums, largest, sizes)
        
        def insert_heap(sorted_nums, num):
            if sorted_nums[0] < num:
                sorted_nums[0] = num
                heapify(sorted_nums, 0, len(sorted_nums))
            return sorted_nums

        sorted_nums = build_heap(val_nums[0:k], k) # 这里与上面的区别是只维护一个k的堆,然后把剩下的部分进行插入。
        
        for i in range(k, len(val_nums)):
            sorted_nums = insert_heap(sorted_nums, val_nums[i])

        out = []
        for i in range(len(sorted_nums)):
            out.append(record2[sorted_nums[i]][0])
            record2[sorted_nums[i]].pop(0)
        return out

Quick Select

先放一个快排的baseline:

import random
def partition(nums, left, right):
    rand = random.randint(left, right)
    nums[rand], nums[right] = nums[right], nums[rand]
    res1 = left
    res2 = left
    tmp = nums[right]

    while res2 < right:
        if nums[res2] <= tmp:
            nums[res1], nums[res2] = nums[res2], nums[res1]
            res2 += 1
            res1 += 1
        else:
            res2 += 1
    nums[right], nums[res1] = nums[res1], nums[right]
    return res1

def quick_sort(nums, left, right):
    if left < right:  ####这里一定是if,切记不是while
        res = partition(nums, left, right)
        quick_sort(nums, left, res-1)
        quick_sort(nums, res+1, right)
    return nums
#Quick select
def quick_select(nums, k, left=0, right=len(nums) - 1):
    res = partition(nums, left, right)
    if res > len(nums) - k:
        target = quick_select(nums, k, left, res-1)
    elif res < len(nums) - k:
        target = quick_select(nums, k, res+1, right)
    else:
        return nums[res]
    return target
import random
class Solution(object):
    def topKFrequent(self, nums, k):

        record = {
    
    }
        for i in nums:
            if i not in record:
                record[i] = 1
            else:
                record[i] += 1
        record2 = {
    
    }
        val_nums = []
        for i in record:
            if record[i] not in record2:
                record2[record[i]] = [i]
            else:
                record2[record[i]].append(i)
            val_nums.append(record[i])
        nums = val_nums


        ## quick select
        def partition(nums, left, tail):
            right = left # left指向的是第一个大于tail的数
            
            rand = random.randint(left, tail)
            nums[rand], nums[tail] = nums[tail], nums[rand]

            while right < tail:
                if nums[right] <= nums[tail]:
                    nums[right], nums[left] = nums[left], nums[right]
                    right += 1
                    left += 1
                else:
                    right += 1
            nums[tail], nums[left] = nums[left], nums[tail]
            return nums, left

        def Quick_select(nums, k):
            left = 0
            right = len(nums) - 1
            index = len(nums) - k
            while left < right:
                nums, sign = partition(nums, left, right)
                if sign > index:
                    right = sign-1
                elif sign < index:
                    left = sign+1
                else:
                    break
            return nums[-k::]
        
        sorted_nums = Quick_select(nums, k)
        out = []
        for i in range(len(sorted_nums)):
            out.append(record2[sorted_nums[i]][0])
            record2[sorted_nums[i]].pop(0)
        return out

猜你喜欢

转载自blog.csdn.net/caihuanqia/article/details/112916569
今日推荐