从例子学分而治之:数组中第k大元素、快排、归并排序的灵魂
2023-09-03 03:47:46
何为分而治之
分而治之,顾名思义,就是把一个复杂的大问题分解成若干个相对独立的小问题,分别解决这些小问题,然后再把它们的结果合并起来,最终得到大问题的解决结果。这种思想广泛应用于各种算法和计算任务中,是算法设计和分析的重要基础。
详解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大元素问题中的应用,并由此延伸,阐述了分治法在快排、归并排序中的应用。希望通过本文,读者能够深入理解分而治之的精髓,并将其应用到自己的算法设计和分析实践中。