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