912. 排序数组 #
链接 #
题目 #
给你一个整数数组 nums
,请你将该数组升序排列。
示例 1:
输入:nums = [5,2,3,1] 输出:[1,2,3,5]
示例 2:
输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]
提示:
1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104
解答 #
堆排序
#include <vector>
using namespace std;
class Solution {
public:
vector<int> sortArray(vector<int> &nums) {
// 调用堆排序函数
heap_sort(nums);
return nums;
}
private:
void heap_sort(vector<int> &arr) {
int n = arr.size();
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--) { // 从最后一个非叶子节点开始
heapify(arr, n, i);
}
// 一个个交换元素
for (int i = n - 1; i > 0; i--) {
swap(arr[0], arr[i]); // 将当前最大的放到数组末尾
heapify(arr, i, 0); // 重新对剩下的元素进行堆调整
}
}
void heapify(vector<int> &arr, int n, int i) { // n 表示数组长度,i 表示当前节点下标
// 第一部分,找到最大值
int largest = i; // 初始化最大为根
int left = 2 * i + 1; // 左 = 2*i + 1
int right = 2 * i + 2; // 右 = 2*i + 2
// 如果左子节点大于根节点
if (left < n && arr[left] > arr[largest]) { // left < n 合法性检查
largest = left;
}
// 如果右子节点比最大还大
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 第二部分,将最大值交换到根
// 如果最大不是根
if (largest != i) {
swap(arr[i], arr[largest]);
// 递归地定义子堆
heapify(arr, n, largest);
}
}
};
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
const heapify = (nums, n, i) => {
let left = 2 * i + 1
let right = 2 * i + 2
let largest = i
if (left < n && nums[left] > nums[largest]) {
largest = left
}
if (right < n && nums[right] > nums[largest]) {
largest = right
}
if(largest != i) {
[nums[largest], nums[i]] = [nums[i], nums[largest]]
heapify(nums, n, largest)
}
}
const n = nums.length
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(nums, n, i)
}
for (let i = n - 1; i >= 0; i--) {
[nums[i], nums[0]] = [nums[0], nums[i]]
heapify(nums, i, 0)
}
return nums
};
可视化:Binary Max Heap | Build-Max-Heap - YouTube
考点:heapify 的时空复杂度分析 #
堆排序算法中的核心函数 heapify
,用于确保从给定的节点 i
开始,下面的子树遵循最大堆的性质。最大堆的性质是父节点的值大于或等于其子节点的值。该函数通过递归调用自身来保证这一点,如果在当前节点、其左子节点或其右子节点中发现更大的值,就会进行交换,并在交换后对影响到的子树再次进行 heapify
调用。
时间复杂度 #
分析 heapify
函数的时间复杂度时,问题实际等价于递归调用的深度与 N 的关系。对于一个完全二叉树,最坏的情况是需要递归地调整到叶节点,这个深度大约是树的高度 (h),其中 (h = \log_2(n))(因为每一层节点数量翻倍,所以树的高度与节点数量的对数成正比)。
最坏情况时间复杂度:在最坏的情况下(即每次调用
heapify
时都需要交换到叶子节点),时间复杂度为 (O(\log n))。平均情况时间复杂度:考虑到不是每次都需要交换到最底层,平均情况下的时间复杂度仍然可以认为是 (O(\log n)),但实际上的常数因子可能会小于最坏情况。
空间复杂度 #
- 递归堆栈空间复杂度:由于
heapify
函数是递归调用的,其空间复杂度主要由递归堆栈的深度决定,即 (O(\log n))。这是因为在最坏的情况下,递归的深度可以达到树的高度,即 (h = \log_2(n))。