返回

征服算法难题:快速找到数组中最小的 K 个数

前端

征服算法难题:寻找数组中最小的 K 个数

踏上编程的冒险之旅,一起征服算法难题吧!今天,我们的目标是找出数组中最小的 K 个数,在这个过程中,我们将深入了解排序和堆数据结构。

排序还是堆?理解基本思路

解决这个问题有两条基本路径:

  1. 排序: 对数组进行排序,然后取前 K 个数。这是一种简单粗暴的方法,但时间复杂度较高。

  2. 堆: 使用堆数据结构来存储数组中的元素,堆顶元素就是当前最小的数。不断从堆中取出元素,直到取出 K 个数。这种方法的时间复杂度更低。

深入算法细节

排序法: 使用快速排序或归并排序等算法对数组进行排序,然后取出前 K 个数。

堆法:

  • 创建最大堆: 最大堆的特点是堆顶元素是堆中最大的元素。
  • 插入元素: 将数组中的元素依次插入堆中。
  • 弹出元素: 从堆中弹出 K 个元素,即为数组中最小的 K 个数。

代码实现

// 使用堆来找到数组中最小的 k 个数

// 创建一个最大堆
const maxHeap = [];

// 将数组中的元素依次插入堆中
for (let i = 0; i < nums.length; i++) {
  insertIntoMaxHeap(maxHeap, nums[i]);
}

// 从堆中弹出 k 个元素
const smallestK = [];
for (let i = 0; i < k; i++) {
  smallestK.push(popFromMaxHeap(maxHeap));
}

// 返回最小的 k 个数
return smallestK;

// 插入元素到最大堆中
function insertIntoMaxHeap(heap, element) {
  // 将元素添加到堆的末尾
  heap.push(element);

  // 将堆调整为最大堆
  heapifyUp(heap, heap.length - 1);
}

// 从最大堆中弹出元素
function popFromMaxHeap(heap) {
  // 将堆顶元素与最后一个元素交换
  const temp = heap[0];
  heap[0] = heap[heap.length - 1];
  heap[heap.length - 1] = temp;

  // 将堆调整为最大堆
  heapifyDown(heap, 0);

  // 弹出最后一个元素
  return heap.pop();
}

// 将堆调整为最大堆(自上而下)
function heapifyDown(heap, index) {
  // 获取左子节点和右子节点的索引
  const leftChildIndex = 2 * index + 1;
  const rightChildIndex = 2 * index + 2;

  // 获取最大元素的索引
  let largestIndex = index;
  if (leftChildIndex < heap.length && heap[leftChildIndex] > heap[largestIndex]) {
    largestIndex = leftChildIndex;
  }
  if (rightChildIndex < heap.length && heap[rightChildIndex] > heap[largestIndex]) {
    largestIndex = rightChildIndex;
  }

  // 如果最大元素的索引不等于当前索引,则交换元素并继续调整
  if (largestIndex !== index) {
    const temp = heap[index];
    heap[index] = heap[largestIndex];
    heap[largestIndex] = temp;

    heapifyDown(heap, largestIndex);
  }
}

// 将堆调整为最大堆(自下而上)
function heapifyUp(heap, index) {
  // 获取父节点的索引
  const parentIndex = Math.floor((index - 1) / 2);

  // 如果父节点的元素小于当前元素,则交换元素并继续调整
  if (parentIndex >= 0 && heap[parentIndex] < heap[index]) {
    const temp = heap[index];
    heap[index] = heap[parentIndex];
    heap[parentIndex] = temp;

    heapifyUp(heap, parentIndex);
  }
}

复杂度分析

  1. 排序法: 时间复杂度为 O(n log n),其中 n 为数组的长度。

  2. 堆法: 时间复杂度为 O(n log k),其中 n 为数组的长度,k 为要找的最小的 k 个数。

结语

征服算法难题并非易事,但只要掌握正确的方法和思路,就能够迎难而上,不断提升自己的编程技能。今天分享的两种方法,希望能够助你一臂之力,让你在算法的世界里更上一层楼。

常见问题解答

  1. 哪种方法更好?
    堆法在时间复杂度上更有优势,尤其是当 k 值较小的时候。

  2. 如何选择 k 值?
    k 值的选择取决于具体的应用场景和需求。

  3. 如何扩展算法来寻找最大的 k 个数?
    只需将最大堆替换为最小堆即可。

  4. 算法还能应用于其他场景吗?
    是的,算法还可以应用于其他场景,如查找中位数、第 k 大元素等。

  5. 如何优化算法的性能?
    可以使用各种优化技术,如空间换时间(预先存储已排序数组)、索引跳跃等。