Bootstrap

Top N 的算法题如何解决

01819abd3fcacfe1c19897eee0140131.png

解决Top N(前N大或前N小)的算法问题通常涉及到对数据进行排序、筛选或堆的操作。以下是一些常见的解决方法:

1、 排序法

对整个数据集进行排序,然后取出前N个元素作为结果。时间复杂度为O(nlogn),适用于数据量不大的情况。

2、 部分排序法

可以使用部分排序算法,比如堆排序中的堆,只维护前N个最大或最小元素。时间复杂度为O(nlogN),适用于大数据集合。

3、 计数法

对数据进行计数,并选择出前N个最大或最小的元素。适用于数据有一定范围的情况。

4、 分治法

使用分治法,将数据分割成不同的部分,只处理包含前N个元素的部分。适用于数据量大且分布均匀的情况。

5、 优先队列(堆): 使用堆数据结构来维护前N个元素,可以实现较高效的Top N查找。时间复杂度为O(nlogN)。

6、 快速选择算法

类似于快速排序的思想,通过每次选择一个基准值,将数据分为比基准值大和小的两部分,只处理包含前N个元素的部分。时间复杂度为O(n)。

根据具体情况选择合适的算法,通常在处理大数据集时,优先考虑堆排序或快速选择算法,因为它们可以在较短的时间内找到Top N元素。

部分排序

function findTopN(arr, n) {
    // 部分排序法
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < arr.length; j++) {
            if (arr[j] > arr[i]) {
                [arr[i], arr[j]] = [arr[j], arr[i]];
            }
        }
    }


    return arr.slice(0, n);
}


// 示例
const arr = [3, 10, 4, 7, 6, 9, 1];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);

计数法

function findTopN(arr, n) {
    // 计数法
    const counts = {};
    arr.forEach(num => {
        counts[num] = (counts[num] || 0) + 1;
    });


    const uniqueNums = Object.keys(counts).map(Number);
    uniqueNums.sort((a, b) => b - a); // 降序排序


    const result = [];
    let count = 0;
    for (let num of uniqueNums) {
        const freq = counts[num];
        for (let i = 0; i < freq && count < n; i++) {
            result.push(num);
            count++;
        }
        if (count >= n) {
            break;
        }
    }


    return result;
}


// 示例
const arr = [3, 10, 4, 7, 6, 9, 1, 10, 10, 7];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);

分治法

function findTopN(arr, n) {
  // 分治法
  function quickSelect(arr, k, left, right) {
      if (left === right) {
          return arr[left];
      }


      let pivotIndex = Math.floor(Math.random() * (right - left + 1)) + left;
      pivotIndex = partition(arr, left, right, pivotIndex);


      if (k === pivotIndex) {
          return arr[k];
      } else if (k < pivotIndex) {
          return quickSelect(arr, k, left, pivotIndex - 1);
      } else {
          return quickSelect(arr, k, pivotIndex + 1, right);
      }
  }


  function partition(arr, left, right, pivotIndex) {
      let pivotValue = arr[pivotIndex];
      [arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];
      let storeIndex = left;


      for (let i = left; i < right; i++) {
          if (arr[i] > pivotValue) {
              [arr[i], arr[storeIndex]] = [arr[storeIndex], arr[i]];
              storeIndex++;
          }
      }


      [arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]];
      return storeIndex;
  }


  const result = [];
  for (let i = 0; i < n; i++) {
      result.push(quickSelect(arr, i, 0, arr.length - 1));
  }


  return result;
}


// 示例
const arr = [3, 10, 4, 7, 6, 9, 1];
const n = 3;
const topN = findTopN(arr, n);
console.log(`前${n}个最大值为:${topN}`);

快速选择算法

快速选择算法(Quickselect)它是快速排序的一种变体。要找到前N个最大的元素,我们可以利用快速选择算法找到第k大的元素,然后对前k大的元素进行筛选。

function partition(arr, left, right) {
  let pivot = arr[right];
  let i = left;
  for (let j = left; j < right; j++) {
      if (arr[j] >= pivot) {  // 注意这里是 >= ,因为我们要找的是前N个最大的元素
          [arr[i], arr[j]] = [arr[j], arr[i]];
          i++;
      }
  }
  [arr[i], arr[right]] = [arr[right], arr[i]];
  return i;
}


function quickselect(arr, left, right, k) {
  if (left <= right) {
      let pivotIndex = partition(arr, left, right);
      if (pivotIndex === k) {
          return arr.slice(0, k + 1);
      } else if (pivotIndex < k) {
          return quickselect(arr, pivotIndex + 1, right, k);
      } else {
          return quickselect(arr, left, pivotIndex - 1, k);
      }
  }
  return [];
}


function findTopNElements(arr, n) {
  if (n <= 0) {
      return [];
  }
  if (n >= arr.length) {
      return arr.slice().sort((a, b) => b - a);
  }
  return quickselect(arr, 0, arr.length - 1, n - 1);
}


// 示例用法
const arr = [3, 2, 1, 5, 6, 4];
const n = 3;
const topNElements = findTopNElements(arr, n);
console.log(`前${n}个最大的元素:`, topNElements);

1、partition函数:这个函数用于将数组分成两部分,以选择的枢轴为基准,将大于等于枢轴的元素放在左边,小于枢轴的元素放在右边。这里选择数组的最后一个元素作为枢轴。

2、quickselect函数:这是一个递归函数,用于在分区后选择第k个最大的元素。如果分区点正好是k,则返回前k个元素。如果分区点小于k,则递归处理右半部分,否则处理左半部分。

3、findTopNElements函数:这是主函数,用于处理特殊情况(如n小于等于0或n大于等于数组长度),并调用quickselect函数。

优先队列

使用优先队列(最小堆)算法找到前N个最大的元素,可以使用JavaScript中的MinHeap来实现。我们将数组中的元素依次插入到一个大小为N的最小堆中,如果堆的大小超过N,就移除堆顶元素(最小的元素)。最终,堆中存放的就是前N个最大的元素。

class MinHeap {
  constructor() {
      this.heap = [];
  }


  insert(value) {
      this.heap.push(value);
      this.bubbleUp();
  }


  bubbleUp() {
      let index = this.heap.length - 1;
      while (index > 0) {
          let parentIndex = Math.floor((index - 1) / 2);
          if (this.heap[parentIndex] <= this.heap[index]) break;
          [this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
          index = parentIndex;
      }
  }


  extractMin() {
      if (this.heap.length === 1) return this.heap.pop();
      const min = this.heap[0];
      this.heap[0] = this.heap.pop();
      this.sinkDown(0);
      return min;
  }


  sinkDown(index) {
      const length = this.heap.length;
      const element = this.heap[index];
      while (true) {
          let leftChildIndex = 2 * index + 1;
          let rightChildIndex = 2 * index + 2;
          let leftChild, rightChild;
          let swap = null;


          if (leftChildIndex < length) {
              leftChild = this.heap[leftChildIndex];
              if (leftChild < element) swap = leftChildIndex;
          }


          if (rightChildIndex < length) {
              rightChild = this.heap[rightChildIndex];
              if ((swap === null && rightChild < element) || (swap !== null && rightChild < leftChild)) {
                  swap = rightChildIndex;
              }
          }


          if (swap === null) break;
          [this.heap[index], this.heap[swap]] = [this.heap[swap], this.heap[index]];
          index = swap;
      }
  }


  size() {
      return this.heap.length;
  }


  peek() {
      return this.heap[0];
  }
}


function findTopNElements(arr, n) {
  if (n <= 0) {
      return [];
  }
  if (n >= arr.length) {
      return arr.slice().sort((a, b) => b - a);
  }


  const minHeap = new MinHeap();
  for (let i = 0; i < arr.length; i++) {
      minHeap.insert(arr[i]);
      if (minHeap.size() > n) {
          minHeap.extractMin();
      }
  }


  const result = [];
  while (minHeap.size() > 0) {
      result.push(minHeap.extractMin());
  }
  return result.sort((a, b) => b - a);  // 最后结果需要从大到小排序
}


// 示例用法
const arr = [3, 2, 1, 5, 6, 4];
const n = 3;
const topNElements = findTopNElements(arr, n);
console.log(`前${n}个最大的元素:`, topNElements);

1、MinHeap类:实现了一个最小堆,包括插入元素、移除最小元素、获取堆的大小、查看堆顶元素等操作。

2、findTopNElements函数:这个函数使用最小堆来存储前N个最大的元素。如果堆的大小超过N,就移除堆顶元素。最终,堆中存放的就是前N个最大的元素。

3、示例用法:创建一个数组和N值,并调用findTopNElements函数来找到前N个最大的元素。最后输出结果。

;