快排解Top-K问题

快排解Top-K问题

Top-k问题一直都是特别重要也是面试很常考的题型(听别人说的,其实我一次也没碰到)。我决定研究top-k问题,总结出来一套使用快速排序思路解决Top-k问题的套路,利用快速排序的思想可以将时间复杂度降低,最好状态时间复杂度O(n)空间O(logn)(参照算法导论,其实我根本没看过),相对来说是很快的啦。阅读本文可以解决一下几道题:

215. 数组中的第K个最大元素

347. 前K个高频元素

973. 最接近原点的K个点

快速排序

快速排序是真的非常重要,写法也有好几种,但是因为太重要,至少要会一种,背也得背下来。我分析一个我自己看着比较舒服的写法。最关键的是它快排的思想来源于分治,除了排序之外还能干很多事。

这里先奉上一个快排的代码,后面我们根据这个改成做题的模版。本身快排可以一个函数写完,但是为了方便后面理解我分成了两个,重点在于第一个partition()函数,可以将一个元素定位到排序后的位置上。

	def partition(nums,left,right):
		temp = left
		x = left+1
		y = right
		while(True):
			while(x<=y and nums[x]<nums[temp]):
				x+=1
			while(x<=y and nums[y]>nums[temp]):
				y-=1
			if x>y:
				break
			nums[x],nums[y] = nums[y],nums[x]
			x+=1
			y-=1
		nums[temp],nums[y] = nums[y],nums[temp]
		return y

	def Sort(nums,l,r):
		if l<r:
			i = partition(nums,l,r)
			Sort(nums,l,i-1)
			Sort(nums,i+1,r)

解题模版

现在有了定位一个元素的方法,我们不需要完整排序一个数组,而是只需找到排序后数组的第k个元素即可。那么我们可以考虑使用二分的方法进一步优化。接下来奉上模版。我注释的地方为实际问题中需要改动的地方。

	def partition(nums,left,right):
		temp = left
		x = left+1
		y = right
		while(True):
			while(x<=y and nums[x]<nums[temp]):#下标x的元素<下标temp的元素大小
				x+=1
			while(x<=y and nums[y]>nums[temp]):#下标y的元素>下标temp的元素大小
				y-=1
			if x>y:
				break
			nums[x],nums[y] = nums[y],nums[x]	#交换下标x的元素和下标y的元素
			x+=1
			y-=1
		nums[temp],nums[y] = nums[y],nums[temp]##交换下标temp的元素和下标y的元素
		return y
	
	def findTopk(nums,k):
		'''
		边界判断和数组的预处理
		'''
		left = 0
		right = n-1
		target = n-k	#如果是找第k大target = n-k,如果是第k小target = k-1
		while(True):
			index = self.partition(nums,left,right)
			if index == target:	#找到了
				#根据问题要求返回结果
			elif index<target:	#如果在右边就向右找
				left = index+1
			else:				#如果在左边就向左找
				right = index-1

开始解题

接下来我们开始套模版

215. 数组中的第K个最大元素

这道题目标是找第k大元素,而且要找的数组是已经给好的,那么我们只需定位到这个元素下标然后返回该元素即可。上代码:

		def partition(self,nums,left,right):
			temp = left
			x = left+1
			y = right
			while(True):
				while(x<=y and nums[x]<nums[temp]):
					x+=1
				while(x<=y and nums[y]>nums[temp]):
					y-=1
				if x>y:
					break
				nums[x],nums[y] = nums[y],nums[x]
				x+=1
				y-=1
			nums[temp],nums[y] = nums[y],nums[temp]
			return y

		def findKthLargest(self, nums, k):
			n = len(nums)
			if n<=0:
				return 0
			left = 0
			right = n-1
			target = n-k	#找第k大选择 target = n-k
			while(True):
				index = self.partition(nums,left,right)
				if index == target:
					return nums[index]	#直接返回对应的元素
				elif index<target:
					left = index+1
				else:
					right = index-1

347. 前K个高频元素

这也是一个前k大的问题,不过人家要把前k个都输出来。而且人家没有直接给你要排序的数组,人家问的是频率,那么就需要我们自己构造这个数组。

不过有了模版就很简单了,我们可以直接用哈希表把频率统计出来,然后构造一个二维的数组,按照频率找top-k,最后输出元素的值即可。

	def partition(self,nums,left,right):
		temp = left
		x = left+1
		y = right
		while(True):
			while(x<=y and nums[x][1]<nums[temp][1]):	#这里改成用频率判断
				x+=1
			while(x<=y and nums[y][1]>nums[temp][1]):	#这里改成用频率判断
				y-=1        
			if x>y:
				break
			nums[x],nums[y] = nums[y],nums[x]
			x+=1
			y-=1     
		nums[temp],nums[y] = nums[y],nums[temp]
		return y
	def topKFrequent(self, nums, k):
		D = collections.Counter(nums)
		L = []
		for i in D.keys():
			L.append((i,D[i]))
		#以上4行为我们自己构造的数组L,其中存放元素和频率的二元组:(元素,频率)
		n = len(L)
		if n<=0:
			return []
		left = 0
		right = n-1
		target = n-k	#找第k大选择 target = n-k
		while(True):
			index = self.partition(L,left,right)
			if index == target:
				return [t[0] for t in L[index:]]	#返回下标从k到n-1的元素
			elif index<target:
				left = index+1
			else:
				right = index-1

973. 最接近原点的K个点

这道题的不同点在于要求的是前k小,所以定义target的时候要修改一下,其他地方都一样。

	def partition(self,nums,left,right):
		temp = left
		x = left+1
		y = right
		while(True):
			while(x<=y and nums[x][2]<nums[temp][2]):	#这里改成用距离判断
				x+=1
			while(x<=y and nums[y][2]>nums[temp][2]):	#这里改成用距离判断
				y-=1        
			if x>y:
				break
			nums[x],nums[y] = nums[y],nums[x]
			x+=1
			y-=1     
		nums[temp],nums[y] = nums[y],nums[temp]
		return y
	def kClosest(self, points, K):
		L= []
		for i in points:
			L.append([i[0],i[1],i[0]**2+i[1]**2])
		n = len(L)
		if n<=0:
			return []
		left = 0
		right = n-1
		target = K-1	#找第k小选择 target = k-1
		while(True):
			index = self.partition(L,left,right)
			if index == target:
				return [[t[0],t[1]] for t in L[:K]]	#返回对应的前k个点
			elif index<target:
				left = index+1
			else:
				right = index-1

最后

快排的模版就介绍完啦。Top-k的问题有很多种解法,我这里只是总结一种解题思路,不能保证复杂度的最优,给刚开始学的小伙伴开阔一下视野,有什么不完善的地方请大佬开喷。

最后我发现leetcode上还有一道top-k问题:692. 前K个高频单词。这道题好像不能直接套用我们的模版,似乎用堆的方法更好,我去研究研究再写。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值