排序


快速排序-桶排序-荷兰国旗问题

求解第 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;

荷兰国旗问题

https://leetcode-cn.com/problems/sort-colors/solution/kuai-su-pai-xu-partition-guo-cheng-she-ji-xun-huan/

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值