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个最大的元素。最后输出结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
字节跳动常见算法面试top50整理如下: 1. 两数之和:给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。 2. 两数相加:给定两个非空链表表示两个非负整数,将两数相加返回一个新的链表。 3. 无重复字符的最长子串:给定一个字符串,请找出其中不含有重复字符的最长子串的长度。 4. 两个排序数组的中位数:给定两个大小分别为 m 和 n 的有序数组 nums1 和 nums2,请找出这两个有序数组的中位数。 5. 电话号码的字母组合:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 6. 四数之和:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a、b、c 和 d,使得 a + b + c + d 的值与 target 相等。 7. 合并两个有序链表:将两个有序链表合并为一个新的有序链表并返回。 8. 验证回文串:给定一个字符串,验证它是否是回文串。 9. 最长有效括号:给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。 10. 二叉树的最大深度:给定一个二叉树,找出其最大深度。 11. 盛最多水的容器:给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai)。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 12. 三数之和:给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ? 13. 最接近的三数之和:给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。 14. 有效的括号:给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 15. 合并两个有序数组:给定两个有序数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 16. 数组中的第K个最大元素:在未排序的数组中找到第 k 个最大的元素。 17. 罗马数字转整数:将罗马数字转换成整数。 18. 最小路径和:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和最小。 19. 矩阵置零:给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。 20. 字符串相乘:给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积。 21.有效的数独:判断一个 9x9 的数独是否有效。 22. 旋转图像:给定一个 n × n 的二维矩阵表示一个图像,将图像顺时针旋转 90 度。 23. 搜索旋转排序数组:假设按照升序排序的数组在预先未知的某个点上进行了旋转。 24. 螺旋矩阵:给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 25. 合并K个排序链表:合并 k 个排序链表,返回合并后的排序链表。 26. 不同路径:一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。 27. 跳跃游戏:给定一个非负整数数组,你最初位于数组的第一个位置。 28. 插入区间:给出一个无重叠的,按照区间起始端点排序的区间列表。 29. 最长公共前缀:编写一个函数来查找字符串数组中的最长公共前缀。 30. 螺旋矩阵 II:给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 31. 编辑距离:给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数。 32. 删除排序链表中的重复元素:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 33. 字符串转整数(atoi):请你来实现一个 atoi 函数,使其能将字符串转换成整数。 34. 平衡二叉树:给定一个二叉树,判断它是否是高度平衡的二叉树。 35. Pow(x, n):实现 pow(x, n),即计算 x 的 n 次幂函数。 36. 搜索二维矩阵:编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。 37. 接雨水:给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 38. 二进制求和:给定两个二进制字符串,返回它们的和(用二进制表示)。 39. 括号生成:给出 n 对括号,请编写一个函数来生成所有的由 n 对括号组成的合法组合。 40. 逆波兰表达式求值:根据逆波兰表示法,求表达式的值。 41. 环形链表:给定一个链表,判断链表中是否有环。 42. 环形链表 II:给定一个链表,返回链表开始入环的第一个节点。 43. 重建二叉树:根据一棵树的前序遍历与中序遍历构造二叉树。 44. 验证二叉搜索树:给定一个二叉树,判断其是否是一个有效的二叉搜索树。 45. 二叉树的中序遍历:给定一个二叉树,返回它的中序 遍历。 46. 最小栈:设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 47. 单词拆分:给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 48. 对称二叉树:给定一个二叉树,检查它是否是镜像对称的。 49. N皇后问:给定一个整数 n,返回所有不同的 n 皇后问解决方案。 50. 跳跃游戏 II:给定一个非负整数数组,你最初位于数组的第一个位置,在该位 我们的主要任务是根据输入来模拟对话。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值