返回

从例子学分而治之:数组中第k大元素、快排、归并排序的灵魂

前端

何为分而治之

分而治之,顾名思义,就是把一个复杂的大问题分解成若干个相对独立的小问题,分别解决这些小问题,然后再把它们的结果合并起来,最终得到大问题的解决结果。这种思想广泛应用于各种算法和计算任务中,是算法设计和分析的重要基础。

详解LeetCode 215:数组中第k大元素

题目
给定一个整数数组 nums 和一个整数 k,找出数组中第 k 大的元素。
请注意,你需要找的是数组排序后的第 k 大元素,而不是第 k 个不同的元素。

示例:
输入:nums = [3,2,1,5,6,4], k = 2
输出:5

解题思路:

  • 方法一:排序后取值
    这是最简单的解法,先将数组 nums 排序,然后直接取排序后的第 k 个元素即可。

    def findKthLargest_sort(nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        nums.sort()  # 排序
        return nums[k - 1]  # 取排序后的第 k 个元素
    
  • 方法二:快速选择
    快速选择是分而治之的典型应用。它先随机选择一个元素作为枢轴,然后将数组划分为两个部分:小于枢轴的元素和大于枢轴的元素。再递归地对这两个部分应用同样的过程,直到找到第 k 大的元素。

    def findKthLargest_quick_select(nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        def quick_select(left, right):
            """
            在 nums[left, right] 范围内找到第 k 大的元素
            """
            # 随机选择一个枢轴
            pivot = random.randint(left, right)
            # 将数组划分为两部分
            nums[pivot], nums[right] = nums[right], nums[pivot]
            i = left - 1
            for j in range(left, right):
                if nums[j] <= nums[right]:
                    i += 1
                    nums[i], nums[j] = nums[j], nums[i]
            # 将枢轴放回正确的位置
            nums[i + 1], nums[right] = nums[right], nums[i + 1]
            # 如果 i + 1 等于 k,则枢轴就是第 k 大的元素
            if i + 1 == k:
                return nums[i + 1]
            # 否则递归地对两部分应用同样的过程
            elif i + 1 < k:
                return quick_select(i + 2, right)
            else:
                return quick_select(left, i - 1)
    
        return quick_select(0, len(nums) - 1)
    
  • 方法三:堆排序
    堆排序也是分而治之的典型应用。它先将数组构建成一个大根堆(或小根堆),然后依次弹出堆顶元素,即可得到排序后的数组。第 k 大的元素就是第 k 次弹出的元素。

    def findKthLargest_heap_sort(nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: int
        """
        # 将数组构建成大根堆
        heapq.heapify(nums)
        # 依次弹出堆顶元素
        for _ in range(k - 1):
            heapq.heappop(nums)
        # 堆顶元素就是第 k 大的元素
        return heapq.heappop(nums)
    

延伸:分治法在快排和归并排序中的应用

分治法是快排和归并排序的核心思想。快排通过不断地将数组划分为两个部分,并递归地对这两个部分应用同样的过程,最终将数组排序。归并排序则通过将数组分解成若干个子数组,对这些子数组进行排序,然后合并这些子数组,得到排序后的数组。

无论是快排还是归并排序,分治法的应用都体现了“化繁为简”的思想,将复杂的大问题分解成若干个相对独立的小问题,逐步解决这些小问题,最终解决大问题。这种思想不仅适用于算法设计和分析,也广泛应用于各种领域,例如计算机科学、数学、工程、经济学等。

结语

分而治之是一种重要的算法思想,它广泛应用于各种算法和计算任务中。本文通过LeetCode 215题为例,详细介绍了分治法在数组中第k大元素问题中的应用,并由此延伸,阐述了分治法在快排、归并排序中的应用。希望通过本文,读者能够深入理解分而治之的精髓,并将其应用到自己的算法设计和分析实践中。