剑指Offer 40. 最小的【K 个】数(Easy)

题解

  1. Python排序算法

代码

  • TopK快排
class Solution:
    def getLeastNumbers(self, nums, k: int):
        def random_quick(l, r, k):
            random_idx = random.randint(l, r)
            nums[r], nums[random_idx] = nums[random_idx], nums[r]

            pivot = nums[r] # 固定中枢值pivot为右边界值
            i = l - 1 # i从左边界开始
            for j in range(l, r):
            	# if nums[j] > pivot: TopK使用
                if nums[j] <= pivot: # 若当前值小于pivot,则与i的元素交换并放在pivot左侧,MinK使用
                    i += 1
                    nums[i], nums[j] = nums[j], nums[i]
            nums[i + 1], nums[r] = nums[r], nums[i + 1] # 交换中枢值pivot到正确位置

            pos = i + 1       # 本次排好的中枢值元素下标
            val = pos - l + 1 # 计算当前值是第多少小的数,val表示第pos - l + 1小的数
            if k < val: random_quick(l, pos - 1, k)         # 第k小的数在pivot左侧
            elif k > val: random_quick(pos + 1, r, k - val) # 第k小的数在pivot右侧

		########## MinK
        if k == 0: return []

        random_quick(0, len(nums)-1, k)
		
		return nums[:k]   # MinK,但返回是无序的!
		
		########## TopK
		nums = [-n for n in nums]
		if k == 0: return []
		random_quick(0, len(nums)-1, k)
		nums = [-n for n in nums]
        return nums[:k] # TopK,但返回是无序的!
  • TopK堆排序
# topK堆排序
class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        if k == 0: return []

        hp = [-x for x in arr[:k]]
        heapq.heapify(hp)
        
        for i in range(k, len(arr)):
            if -hp[0] > arr[i]:
                heapq.heappop(hp)
                heapq.heappush(hp, -arr[i])
                
        return [-x for x in hp]

# topK堆排序
class Solution:
    def getLeastNumbers(self, nums: List[int], k: int) -> List[int]:
        def buildHeap(heap):
            for i in range(len(heap) // 2, -1, -1):
                heapify(heap, i)

        def heapify(arr, i):
            length = len(arr)
            left, right, largest = 2 * i + 1, 2 * i + 2, i

            if left < length and arr[left] < arr[largest]:
                largest = left
            if right < length and arr[right] < arr[largest]:
                largest = right
            if largest != i:
                arr[i], arr[largest] = arr[largest], arr[i]
                heapify(arr, largest)

        if k == 0: return []

        ######################################################
        # TopK:小小小顶堆
        heap = nums[:k]
        buildHeap(heap) # 构建小顶堆
        
        for i in range(k, len(nums)):
            # 若当前元素 > 堆顶元素,则调整堆
            if nums[i] > heap[0]:
                heap[0] = nums[i]
                heapify(heap, 0) # 这里只需调整堆顶

        return heap # TopK,但返回是无序的!

        ######################################################
        # MinK:大大大顶堆
        heap = [-num for num in nums[:k]]
        buildHeap(heap) # 构建大顶堆

        for i in range(k, len(nums)):
            # 若当前元素取反 > 堆顶元素,则调整堆
            if -nums[i] > heap[0]:
                heap[0] = -nums[i] # 取反入堆!
                heapify(heap, 0) # 这里只需调整堆顶

        return [-num for num in heap] # MinK,但返回是无序的!

分隔线

  • Python排序算法
class Solution:
    ### 1109 插入排序(超时)
    # 从第二个元素开始和前面的元素进行比较(逆序比较),如果前面的元素比当前元素大,则将前面元素后移一位;当前元素继续往前比较,直到找到比它小或等于它的元素并插入在其后面
    # 然后选择第三个元素,重复上述操作,进行插入,依次选择到最后一个元素,插入后即完成所有排序
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def insertionSort(arr):
            for i in range(1, len(arr)):
                curr = arr[i]
                pre_idx = i - 1
                while pre_idx >= 0 and arr[pre_idx] >= curr: # 若前一个元素存在且前一个元素大于当前元素
                    arr[pre_idx + 1] = arr[pre_idx]          # 前一个元素后移一位
                    pre_idx -= 1                             # 继续向前查找
                arr[pre_idx + 1] = curr                      # 直到找到不大于当前元素的元素,插入其后
            return arr
        
        return insertionSort(arr)[: k]
    
    ### 1109 选择排序(超时)
    # 设第一个元素为比较元素,依次和后面的元素比较,比较完所有元素找到最小的元素,将它和第一个元素互换,重复上述操作
    # 找出第二小的元素和第二个位置的元素互换,以此类推找出剩余最小元素将它换到前面,即完成排序
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def selectionSort(arr):
            for i in range(len(arr) - 1):                   # 从第一个元素到倒数第二个元素
                min_idx = i                                 # 每轮假设第一个元素为最小元素
                for j in range(i, len(arr)):                # 遍历i之后的所有元素,若存在比当前最小元素更小的元素,则更新
                    if arr[min_idx] > arr[j]:
                        min_idx = j                         # 更新最小元素下标
                arr[min_idx], arr[i] = arr[i], arr[min_idx] # 每轮结束后,交换当前元素i和找到的最小元素
            return arr

        return selectionSort(arr)[: k]

    ### 1109 冒泡排序(超时)
    # 从第一个和第二个开始比较,如果第一个比第二个大,则交换位置,然后比较第二个和第三个,逐渐往后,经过第一轮后最大的元素已经排在最后
    # 重复上述操作的话第二大的则会排在倒数第二的位置;重复上述操作n-1次即可完成排序,因为最后一次只有一个元素所以不需要比较
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def bubbleSort(arr):
            for i in range(len(arr) - 1):                   # 从第一个元素到倒数第二个元素
                for j in range(len(arr) - 1 - i):           # 从第一个元素到未排序的n-1-i个元素,最后i个元素已排序完成
                    if arr[j] > arr[j+1]:
                        arr[j], arr[j+1] = arr[j+1], arr[j] # 相邻两个元素依次顺序对比,若大小顺序相反则交换
            return arr

        return bubbleSort(arr)[: k]

    ### 1109 快速排序
    # 从原数组中随机选择一个值作为中枢值,数组中剩下的每一个元素与中枢值进行比较
    # 小于中枢值的元素存放到一个子数组,大于中枢值的元素存放到另一个子数组,对子数组递归进行上述操作,直至子数组长度为0或1时返回子数组,开始返回递归
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def quickSort(arr):
            # 若子数组只含有0个或1个元素,则默认为有序数组,直接返回
            if len(arr) < 2:
                return arr
            
            ## 以下3中初始化中枢值的方法,选择一种执行即可
            pivot_idx = 0                       # 1.初始化中枢值为数组arr的第一个元素  (928 ms,24.1 MB)
            pivot_idx = -1                      # 2.初始化中枢值为数组arr的最后一个元素(248 ms,15 MB)
            pivot_idx = len(arr) // 2           # 3.初始化中枢值为数组arr的中间元素    (232 ms,14.8 MB)
            pivot = arr[pivot_idx]              
            arr.pop(pivot_idx)                  # 去掉中枢元素

            lt = [i for i in arr if i <= pivot] # 小于pivot的子数组lt
            gt = [i for i in arr if i > pivot]  # 大于pivot的子数组gt

            return quickSort(lt) + [pivot] + quickSort(gt) # 返回值:较小值子数组 + 中枢值 + 较大值子数组
        
        return quickSort(arr)[: k]

    ### 1110 归并排序(460 ms,14.8 MB)
    # 使用二分法将原数组不断两两划分,直至达到原子数组,然后返回递归;在返回过程中对子数组进行排序,再两两合并,直至合并到原数组长度(分而治之)
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def mergeSort(arr):
            # 达到原子数组,直接返回
            if len(arr) < 2:
                return arr
            
            # 确定中间元素下标,二分法
            mid = len(arr) // 2
            # 根据中间元素将原数组划分为左右两个子数组,两个子数组分别进行归并排序
            l_arr, r_arr = mergeSort(arr[:mid]), mergeSort(arr[mid:])
            
            # 新建一个辅助数组
            sorted_arr = []
            # 当左右子数组中均不为空时,进行循环
            while len(l_arr) > 0 and len(r_arr) > 0:
                # 依次顺序比较左右子数组中元素大小,将小的一个存入辅助数组中
                if l_arr[0] > r_arr[0]:
                    sorted_arr.append(r_arr.pop(0))
                else:
                    sorted_arr.append(l_arr.pop(0))
            
            # 追加可能剩余的元素
            sorted_arr += l_arr
            sorted_arr += r_arr

            return sorted_arr

        return mergeSort(arr)[: k]

    ### 1110 堆排序(356 ms,14.4 MB)
    # 堆分为最大堆和最小堆,是完全二叉树;堆排序就是把堆顶的最大数取出,将剩余的堆继续调整为最大堆
    # 剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        def heapify(arr, heap_size, parent_idx):
            parent_val = arr[parent_idx]   # 暂存父节点元素,用于和下面的节点进行对比
            child_idx = parent_idx * 2 + 1 # 计算(左)子节点下标
            
            # 当子节点未越界时,进行循环
            while child_idx < heap_size:
                # 若此父节点存在右子节点,且右子节点大于左子节点,则更新子节点下标为右子节点
                if child_idx + 1 < heap_size and arr[child_idx] < arr[child_idx + 1]:
                    child_idx += 1
                    
                # 若父节点大于或等于最大子节点,则可以停止比较,退出循环
                if parent_val >= arr[child_idx]:
                    break
                    
                # 若父节点小于最大子节点,则替换父节点和子节点的元素和下标
                arr[parent_idx] = arr[child_idx]
                parent_idx = child_idx
                child_idx = parent_idx * 2 + 1 # 继续循环比较下面的节点
                
            # 若不存在大于父节点的子节点,则父节点不动;否则将原始的父节点元素下沉到叶子结点
            arr[parent_idx] = parent_val
            
        def heapSort(arr):
            # 第一次对原数组构建最大堆时,因为知道所有的非叶节点,因此从最后一个非叶结点(下标为len(arr)//2 - 1)开始向上遍历全部非叶结点,无需从根节点遍历
            for parent_idx in range(len(arr)//2 - 1, -1, -1):
                heapify(arr, len(arr), parent_idx)              # parent_idx表示非叶节点
            
            # 第二次开始则从根节点构建最大堆(无法直接知道所有非叶节点),且每次减少一个元素
            for heap_size in range(len(arr) - 1, 0, -1):
                arr[0], arr[heap_size] = arr[heap_size], arr[0] # 交换最大根和最后一个元素
                heapify(arr, heap_size, 0)                      # heap_size表示当前待排序堆的数组长度,0表示根节点
                
            return arr
        
        return heapSort(arr)[: k]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值