Top K 问题

本文探讨了从数组中找出最大K个数的TopK问题,并详细介绍了四种不同的解法,包括排序、局部排序、使用小顶堆以及快速选择算法。每种方法都附有时间复杂度分析,帮助读者理解各种算法的优劣。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Top K 问题就是从一个数组中找打最大的 K 个数。

 

解法一:排序,取连续 K 个数

将数组从大到小排序后,前面 K 个数就是 Top K。

该解法的时间复杂度在于排序。所选排序算法的不同,会影响到执行效率。

所以可以选一个时间复杂度为 O(n*lg(n)) 的算法,即该解法的时间复杂度为 O(n*lg(n))

 

 

解法二:局部排序,取连续 K 个数

这是对解法一的改进。即,在排序过程中,排出最大的 K 个数时就停止排序,不需要对整个数组进行排序。

大致过程:

  • 扫描数组中的元素,把其中最大的数移到数组的最左边;
  • 重复执行该操作 K 次,就得到了 Top K
    • 注意每次都是针对右侧子数组进行排序,不包括上一次排序得到的最大数

该解法时间复杂度为 O(n*k)

 

 

解法三:先取 K 个数,遍历剩余元素,将其和这 K 个数进行比较,如果比这个 K 个数中的最小值大,则执行替换操作

这是对解法二的改进。即,不对 Top  K 元素排序。

事实上,为了加快剩余元素和已有 K 个数的比较,会将 K 个数存放在一个小顶堆中。

也就是,从严格意义上说,Top K 是经过排序的,但是操作小顶堆的时间复杂度为 O(lg(k))。

该解法的时间复杂度为 O(n*lg(k))。

 

 

解法四:用减治法递归划分出 Top K

大致过程:

  • 用首个元素将数组分成左右两部分,左边的都比它大,右边的都比它小;
  • 如果左边刚好是 K 个数,它们就是 Top K;
  • 如果左边多于 K 个数,则再对左边进行划分;
  • 如果左边少于 K 个数,则再对右边进行划分作为补充;
  • 最终递归得到 Top K

该解法的时间复杂度为 O(n)

 

List<Integer> getTopK(int[] arr, int k) {
    int last;

    if (k <= 0) {
        last = -1;
    } else if (k >= arr.length) {
        last = arr.length - 1;
    } else {
        last = partition(arr, 0, arr.length - 1, k);
    }

    ArrayList<Integer> result = new ArrayList<>(last + 1);
    for (int i = 0; i <= last; i++) {
        result.add(arr[i]);
    }

    return result;
}

int partition(int[] arr, int low, int high, int k) {
    if (k >= high - low + 1) {
        return high;
    }

    int mid = low;
    for (int i = low + 1; i <= high; i++) {
        if (arr[i] > arr[mid]) {
          pop(arr, mid, i);
          mid++;
        }
    }

    int largerCount = mid - low;
    if (largerCount == k) {
        return mid - 1;
    } else if (largerCount + 1 == k) {
        return mid;
    } else if (largerCount == 0) {
        return partition(arr, low + 1, high, k - 1);
    } else if (largerCount > k) {
        return partition(arr, low, mid - 1, k);
    } else {
        return partition(arr, mid, high, k - largerCount);
    }
}

void pop(int[] arr, int first, int last) {
    int tmp = arr[last];
    for (int i = last; i > first; i--) {
        arr[i] = arr[i - 1];
    }
    arr[first] = tmp;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值