目录
快速排序-桶排序-荷兰国旗问题
求解第 K 个元素或前K个元素,使用堆排序、桶排序、快速排序
在大量元素中求topK,可以使用堆排序、优先队列,使得时间复杂度由 o(nlogn) 将为 o(nlogK)
堆排序
- 在大量元素中求topK,可以使用堆排序、优先队列,使得时间复杂度由 o(nlogn) 将为 o(nlogK)
- 大顶堆中每个父节点大于子节点,小顶堆每个父节点小于子节点
堆的两种重要操作,小顶堆为例:
- 上浮,向堆尾新加入一个元素,依次向上与父节点比较,如小于父节点就交换
- 这个还算好写 比较parent 和 cur
- 下沉,更新堆中一个元素,依次向下与子节点比较,如大于子节点就交换
- 这个主要是最小孩子节点的处理 因为有左孩子 右孩子(可能没有) 那么不用区分left_child right_child 只要判断cur有没有left child
- 当有left child的时候 再判断有没有right child ;如果right child 有 and right child值更小的话 就把child_idx+=1
- 这样相当于每次选min(值)的child idx 再进行比较交换即可
快速排序
分治思想
- partition(切分)操作总能排定一个元素,还能够知道这个元素它最终所在的位置,这个元素左边的数都不大于它,这个元素右边的数都不小于它。
- 每次def partition(self,nums,left,right),返回left位于nums的idx;由于极端情况,每次随机从left-right中选一个数和left的数进行交换 ;个人认为最关键的是理解 partition(self,nums,left,right) 中的j和i的比较以及交换
- 注意极端情况可能会使得递归栈加深,可以每次使用初始化left的元素来减缓,比如找最大的元素,然后快速排序 [1,2,3,…,9,10],第一次找到1,第2次找到2,第3次找到3,…,第10次才找到10;
荷兰国旗问题
2020/12/3
215. 数组中的第K个最大元素
快速排序
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
l=0
h=len(nums)-1
k=h+1-k
index=self.partition(nums,l,h)
while(index!=k):
if(index>k):
h=index-1
index=self.partition(nums,l,h)
if(index<k):
l=index+1
index=self.partition(nums,l,h)
return nums[k]
#返回基准的index
def partition(self,nums,l,h):
x0=nums[l]
while(l<h):
while(l<h and nums[h]>x0):
h-=1
# if(nums[h]<x0):
nums[l]=nums[h]
#l+=1
while(l<h and nums[l]<=x0):
l+=1
# if(nums[l]>x0):
nums[h]=nums[l]
#h-=1
nums[l]=x0
return l
堆排序
写的有点臃肿 刚开始的代码少了两行 导致忽略了[此次循环 2*start+1>end 但是此次判断的childIndex是上次的childIndex]这种情况
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
minhead=[]
for i in range(0,k):
minhead.append(nums[i])
#和上比较
self.shiftUp(i,minhead)
#print(minhead)
for num in nums[k:]:
if(num>minhead[0]):
minhead[0]=num
#和下比较
self.shiftDown(0,k-1,minhead)
#print(minhead)
return minhead[0]
#向上比较
def shiftUp(self,index,minhead):
curIndex=index
curVal=minhead[index]
parentIndex=int((index-1)/2)#父节点index
while(parentIndex>=0 and minhead[parentIndex]>curVal):
#父节点>当前值 就把父节点值赋予当前节点
minhead[curIndex]=minhead[parentIndex]
minhead[parentIndex]=curVal
curIndex=parentIndex
parentIndex=int((curIndex-1)/2)
#minhead[curIndex]=curVal
#向下比较 开始的index 结束的index
def shiftDown(self,start,end,minhead):
curVal=minhead[start]
childIndex=end+1
flag=True
while flag:
#找到最小的孩子节点index
if(2*start+1<=end):
childIndex=2*start+1
else:#之前没写这两行 导致结果不对
flag=False#之前没写这两行 导致结果不对
if(childIndex+1<=end and minhead[childIndex+1]<minhead[childIndex]):
childIndex=childIndex+1
#最小孩子节点小于val
if(childIndex<=end and minhead[childIndex]<curVal):
#把孩子赋给当前节点
minhead[start]=minhead[childIndex]
minhead[childIndex]=curVal
start=childIndex
else:
flag=False
稍微精简一下
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
minhead=[]
for i in range(0,k):
minhead.append(nums[i])
#和上比较
self.shiftUp(i,minhead)
for num in nums[k:]:
if(num>minhead[0]):
minhead[0]=num
#和下比较
self.shiftDown(0,k-1,minhead)
return minhead[0]
#向上比较
def shiftUp(self,index,minhead):
curVal=minhead[index]
parentIndex=int((index-1)/2)#父节点index
while(parentIndex>=0 and minhead[parentIndex]>curVal):
#父节点>当前值 就把父节点值赋予当前节点
minhead[index]=minhead[parentIndex]
minhead[parentIndex]=curVal
index=parentIndex
parentIndex=int((index-1)/2)
#向下比较 开始的index 结束的index
def shiftDown(self,start,end,minhead):
curVal=minhead[start]
while(2*start+1<=end):
childIndex=2*start+1
if(childIndex+1<=end and minhead[childIndex+1]<minhead[childIndex]):
childIndex=childIndex+1
if(minhead[childIndex]<curVal):
#把孩子赋给当前节点
minhead[start]=minhead[childIndex]
minhead[childIndex]=curVal
start=childIndex
else:
break
2020/12/6
347. 前 K 个高频元素
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
from collections import Counter
cnt = dict(Counter(nums))
cntList=sorted(cnt.items(),key=lambda x:x[1], reverse = True)
result=[x[0] for x in cntList[:k]]
return result
桶排序 每个桶存储出现频率相同的数。桶的下标表示数出现的频率,即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
o(n)
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
from collections import Counter
cnt = dict(Counter(nums))# num:频率
buckets=[]
for i in range(0,len(nums)+1):
buckets.append([])#每个桶是一个list
for n,v in cnt.items():
buckets[v].append(n)
topK=[]#result
for i in range(1,len(nums)+1):
bucketSize=len(buckets[-i])
if(bucketSize>0 and k-len(topK)>=bucketSize):
topK.extend(buckets[-i])
return topK
451. 根据字符出现频率排序
class Solution:
def frequencySort(self, s: str) -> str:
from collections import Counter
cnt=dict(Counter(s)) # char:频率
buckets=[]#桶 下标为频率
for i in range(0,len(s)+1):
buckets.append([])
for char,count in cnt.items():
buckets[count].append(char)
result=''
#从后向前遍历桶
for i in range(len(s),0,-1):
if(len(buckets[i])>0):
for char in buckets[i]:
result=result+char*i
return result
75. 颜色分类
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
p0=0
p2=len(nums)-1
i=0
while(i<=p2):
#如果i是2 就和后面的p2位置元素交换 如果换回来还是2 接着换
while(i<=p2 and nums[i]==2):
nums[i],nums[p2]=nums[p2],nums[i]
p2-=1
#如果i是0 就和前面的p0位置交换
if(nums[i]==0):
nums[i],nums[p0]=nums[p0],nums[i]
p0+=1
#i往前走1
i+=1
类似于三分partiton
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 0 0 0 1 1 1 2 2 2
# [0,one_start) [one_start,two_start) [two_start,len(nums)-1]
one_start=0
two_start=len(nums)
def swap(nums,idx1,idx2):
nums[idx1],nums[idx2]=nums[idx2],nums[idx1]
i=0
while(i<two_start):
# ==0 影响 one_start
if(nums[i]==0):
swap(nums,i,one_start) # 用当前的0换one_start位置元素(0/1)
one_start+=1
i+=1 # 交换回来肯定是0/1,所以继续i+=1即可
# ==1 不影响 one_start 和 two_start(实际影响,但code中只以num=2来影响)
elif(nums[i]==1):
i+=1
# ==2 影响two_start
else:
two_start-=1
swap(nums,i,two_start)
# 把two_start的数换过来,继续 0/1/2的判断
2021/6/11
347. 前 K 个高频元素 桶排序
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
# 1 计数
from collections import Counter,defaultdict
self.cnt=dict(Counter(nums))
# 2 最小堆
minhead=[]
idx=0
for num in self.cnt.keys():
if(idx<k):
minhead.append(num)
self.shift_up(minhead,idx)
else:
if(self.cnt[num]>self.cnt[minhead[0]]):
minhead[0]=num
self.shift_down(minhead,0,k-1)
idx+=1
return minhead
# 堆尾新增一个元素 向上比较
def shift_up(self,minhead,idx):
# 只要cur val 小于parent;就和parent进行交换
# parent: (idx-1)//2
cur_num=minhead[idx]
# 当parent节点存在 且不是自己的时候
parent_idx=(idx-1)//2
while(0<=parent_idx < idx):
# 比较num对应的cnt
parent_num=minhead[parent_idx]
if(self.cnt[parent_num]<=self.cnt[cur_num]):
break
else:
# 交换
minhead[parent_idx],minhead[idx]=minhead[idx],minhead[parent_idx]
idx=parent_idx
cur_num=minhead[idx]
parent_idx=(idx-1)//2
# 更新首元素 向下比较
def shift_down(self,minhead,cur_idx,end_idx):
# 只要cur val 大于child;就和最小的child进行交换
# left child: 2*idx+1
# right child: 2*idx+2
cur_num=cur_idx
child_idx=2*cur_idx+1
# 当左孩子存在的时候
while(cur_idx<child_idx<=end_idx):
# 选择取值最小的孩子节点
if(child_idx+1<=end_idx and self.cnt[minhead[child_idx+1]]<self.cnt[minhead[child_idx]]):
child_idx=child_idx+1
# 和最小的孩子比较
if(self.cnt[minhead[child_idx]]>=self.cnt[minhead[cur_idx]]):
break
# 交换
else:
minhead[cur_idx],minhead[child_idx]=minhead[child_idx],minhead[cur_idx]
cur_idx=child_idx
child_idx=2*cur_idx+1
215. 数组中的第K个最大元素 快速排序
import random
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
left=0
right=len(nums)-1
target=len(nums)-k # 从小到大排 的idx
while(True):
idx=self.partition(nums,left,right)
if(idx==target):
return nums[idx]
elif(idx<target):
left=idx+1
else:
right=idx-1
# 返回left的idx
def partition(self,nums,left,right):
# 随机初始化一个数和left交换
rand_num=random.randint(left,right)
nums[left],nums[rand_num]=nums[rand_num],nums[left]
left_val=nums[left]
j=0
for i in range(left+1,right+1):
# 把小于等于left_val的数移到左边
if(nums[i]<left_val):
j+=1 # 所以j就是 <left_val的数量 也就是left应该所在的idx
nums[i],nums[left+j]=nums[left+j],nums[i]
# 把left放到应该的idx即j上去,而nums[j]肯定小于left 所以交换即可
nums[left],nums[left+j]=nums[left+j],nums[left]
return left+j