快速排序的框架
912. 排序数组
给你一个整数数组 nums,请你将该数组升序排列。
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
partition函数
- 在
arr[left, right]
选择中枢点arr[mid]
,双指针相向移动,进行交换排序 - 当
left > right
时停止,此时有两种可能:-
arr = [1, 2, 3, 4, 5], pivot = 3
,left
和right
都指向3,那么移动后left
指向4,right
指向2。两个指针隔一个数,且中间那个数就是pivot
。 -
arr = [1, 3, 3', 5], pivot = 3
,left
和right
分别指向3和3',那么swap后arr = [1, 3', 3, 5]
,left
指向3,right
指向3'。两个指针相邻。
-
- 所以,令
index1 = right
,index2 = left
,返回
quickSort函数
-
arr[left, index1]
都是小于等于pivot
,arr[index2, right]
都是大于等于pivot
。在这两个区间分别quickSort。
注意
- 如果只返回一个index,可能会死循环...
- 两个函数可以合并写
- 也可以选择
arr[left]
或arr[right]
为pivot
,就有另一种写法,似乎是同向双指针,没仔细研究。
时间复杂度
画出递归树
n 1 * n
/ \
n/2 n/2 2 * n/2
/ \ / \
n/4 n/4 n/4 n/4 4 * n/4
1 * n + 2 * n/2 + ... + 2^i * n/2^i = n * i = nlog(n)
代码
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
# 面试中可能不允许修改原数组,所以把nums拷贝成arr
arr = [0] * len(nums)
for i in range(len(nums)):
arr[i] = nums[i]
# 进行快速排序
self.quickSort(arr, 0, len(nums) - 1)
return arr
# 归并排序函数:将nums[left...right]排序
def quickSort(self, arr: List[int], left: int, right: int):
if left >= right:
return
# 找到两个中枢点
index1, index2 = self.partition(arr, left, right)
# 左边排序
self.quickSort(arr, left, index1)
# 右边排序
self.quickSort(arr, index2, right)
def partition(self, arr: List[int], left: int, right: int):
pivot = arr[left + (right- left) // 2]
# 当left == right时也要做处理
while left <= right:
# 找到左边第一个大于等于pivot的数
while left <= right and arr[left] < pivot:
left += 1
# 找到右边第一个小于等于pivot的数
while left <= right and arr[right] > pivot:
right -= 1
# 交换
if left <= right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
# [..., right]都是小于等于pivot的
# [left, ...]都是大于等于pivot的
# 有两种可能:right + 1 == left or right + 2 == left
return right, left
快速选择的框架
215. 数组中的第K个最大元素
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
partition函数
和快排一样。
quickSelect函数
- 快排是:1. 找中枢点 2. 快排左和右
- 快选是:1. 找中枢点 2. 快选左或右
时间复杂度
- 平均: n + n/2 + n/4 +... = O(n)
- 最差: n + n-1 + n-2 + ... + 1 = O(n^2)
代码
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 面试中可能不允许修改原数组,所以把nums拷贝成arr
arr = [0] * len(nums)
for i in range(len(nums)):
arr[i] = nums[i]
# 进行快速排序
self.quickSelect(arr, 0, len(nums) - 1, len(nums) - k)
return arr[len(nums) - k]
# 归并排序函数:将nums[left...right]排序
def quickSelect(self, arr: List[int], left: int, right: int, k: int):
# 也可以写 if left == right
if left >= right:
return
# 找到两个中枢点
index1, index2 = self.partition(arr, left, right)
# 左边排序
if k <= index1:
self.quickSelect(arr, left, index1, k)
# 右边排序
elif k >= index2:
self.quickSelect(arr, index2, right, k)
else:
return
def partition(self, arr: List[int], left: int, right: int):
pivot = arr[left + (right- left) // 2]
# 当left == right时也要做处理
while left <= right:
# 找到左边第一个大于等于pivot的数
while left <= right and arr[left] < pivot:
left += 1
# 找到右边第一个小于等于pivot的数
while left <= right and arr[right] > pivot:
right -= 1
# 交换
if left <= right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
# [..., right]都是小于等于pivot的
# [left, ...]都是大于等于pivot的
# 有两种可能:right + 1 == left or right + 2 == left
return right, left