Solution 1: 归并法
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
# Solution 1: 归并排序
n = len(nums)
# 递归结束条件:数字中只有一个元素,返回数组自己
if n == 1:
return nums
# 分成左右两个数组
mid = int(n/2)
sorted_left = self.sortArray(nums[0:mid]) # 递归调用给左侧数组排序
sorted_right = self.sortArray(nums[mid:]) # 递归调用给右侧数组排序
# 合并排序后左侧数组和右侧数组
i, j, n_left, n_right = 0, 0, len(sorted_left), len(sorted_right)
sorted_num = []
while i < n_left and j < n_right:
if sorted_left[i] <= sorted_right[j]:
sorted_num.append(sorted_left[i])
i += 1
else:
sorted_num.append(sorted_right[j])
j += 1
if i == n_left and j < n_right:
# 右侧数组没有遍历完
while j < n_right:
sorted_num.append(sorted_right[j])
j += 1
elif i < n_left and j == n_right:
# 左侧数组没有遍历完
while i < n_left:
sorted_num.append(sorted_left[i])
i += 1
return sorted_num
这次手撕代码一次写完,没有bug,顺利通过,太开心了
- 时间复杂度:O(NlogN)
- 空间复杂度:O(N)
怎么分析时间复杂度和空间复杂度也是有些讲究的,请见官方题解
https://leetcode-cn.com/problems/sort-an-array/solution/pai-xu-shu-zu-by-leetcode-solution/
合并排序后的左侧数组和右侧数组,定义成一个子函数,修改下代码的写法如下:
class Solution:
'''
定义子函数:合并排序后的左侧数组和右侧数组
'''
def mergeSortNum(self, sorted_left: List[int], sorted_right: List[int]) -> List[int]:
# 合并排序后左侧数组和右侧数组
i, j, n_left, n_right = 0, 0, len(sorted_left), len(sorted_right)
sorted_num = []
while i < n_left and j < n_right:
if sorted_left[i] <= sorted_right[j]:
sorted_num.append(sorted_left[i])
i += 1
else:
sorted_num.append(sorted_right[j])
j += 1
if i == n_left and j < n_right:
# 右侧数组没有遍历完
while j < n_right:
sorted_num.append(sorted_right[j])
j += 1
elif i < n_left and j == n_right:
# 左侧数组没有遍历完
while i < n_left:
sorted_num.append(sorted_left[i])
i += 1
return sorted_num
def sortArray(self, nums: List[int]) -> List[int]:
# Solution 1: 归并排序
n = len(nums)
# 递归结束条件:数字中只有一个元素,返回数组自己
if n == 1:
return nums
# 分成左右两个数组
mid = int(n/2)
sorted_left = self.sortArray(nums[0:mid]) # 递归调用给左侧数组排序
sorted_right = self.sortArray(nums[mid:]) # 递归调用给右侧数组排序
sorted_num = self.mergeSortNum(sorted_left, sorted_right) # 合并排序后的左侧数组和右侧数组
return sorted_num
Solution 2: 快排
class Solution:
'''
定义子函数:随机挑选target,快排,返回target的位置
'''
def randomPartition(self, nums: List[int]):
# 随机挑选一个target
n = len(nums)
target = random.randint(0, n-1)
nums[n-1], nums[target] = nums[target], nums[n-1]
# 从头开始遍历nums
pos = -1
for i in range(0, n-1):
if nums[i] <= nums[n-1]:
pos += 1
nums[pos], nums[i] = nums[i], nums[pos]
pos += 1
nums[pos], nums[n-1] = nums[n-1], nums[pos]
return pos
def sortArray(self, nums: List[int]) -> List[int]:
n = len(nums)
if n == 1 or n == 0:
return nums
pos = self.randomPartition(nums)
sorted_left = self.sortArray(nums[0:pos])
sorted_right = self.sortArray(nums[pos+1:])
res = []
res.extend(sorted_left)
res.append(nums[pos])
res.extend(sorted_right)
return res
快排算法的思想有点巧妙,时间复杂度和空间复杂度请参考题解吧
Solution 3 : 堆排序
-
关键知识点(1):一般用数组来表示堆,下标为 i 的结点的父结点下标为(i-1)/2;其左右子结点分别为 (2i + 1)、(2i + 2)
对应代码Note(1)
参考文献:https://zhuanlan.zhihu.com/p/124885051 -
关键知识点(2):即节点总数/2(根节点索引为0),这也就是第一个叶子节点,所以第一个非叶子节点的索引就是第一个叶子结点的索引-1。那么对于填不满的二叉树呢?这个计算方式仍然适用
对应代码Note(2)
参考链接:
https://blog.csdn.net/qq_28063811/article/details/93034625
class Solution:
def heapify(self, heap: List[int], i: int, n: int):
# n = len(heap)
# i -> root
left = 2*i + 1 # Note(1)关键知识点(1)
right = 2*i + 2
swap = i # 默认要交换的节点是i
if left < n and heap[left] > heap[swap]:
# 如果存在左节点且左节点数字比swap大,将swap更新成left
swap = left
if right < n and heap[right] > heap[swap]:
# 如果存在右节点且右节点数字比swap大,将swap更新成right
swap = right
if swap != i:
# 如果swap更新,那么互换swap和i的数字
heap[swap], heap[i] = heap[i], heap[swap]
# 互换之后,如果swap位置还有子节点,重复heapfity操作
# print('swap', swap, nums)
self.heapify(heap, swap, n)
def buildMaxHeap(self, heap: List[int]):
n = len(heap)
# print('build', nums, n)
# Note(2) 从第一个有子节点的节点开始,调整节点的顺序,参见关键知识点(2)
for i in range(int(n/2)-1, -1, -1):
# print('build round', i)
self.heapify(heap, i, n)
def sortArray(self, nums: List[int]) -> List[int]:
# 初始化最大堆
self.buildMaxHeap(nums)
for i in range(len(nums)-1, 0, -1):
nums[0], nums[i] = nums[i], nums[0] # 堆顶元素
# 调整最大堆
# print(i, nums, nums[0:i])
self.heapify(nums, 0, i) # Note(*)
return nums
- Note(*)这里很关键,涉及到python函数传参的问题,如果写成
self.heapify(nums[0:i], 0, i)
注意第一个参数是nums[0:i]而不是nums,那么运行函数heapifty不会改变nums
如果调用
self.buildMaxHeap(nums)
则会超出时间限制,没有必要build,只要heapify第0个元素就实现了
基础的排序算法是很重要的,也要清楚它们的时间复杂度和空间复杂度分析~