剑指offer (30) 最小的K个数

题目:输入n个整数,找出其中最小的K个数

方法一:直接std::sort,T(n) = O(nlgn)

方法二:直接std::nth_element T(n) = O(n) 但是修改了原数组

void MinKth(std::vector<int>& num, int kth, std::vector<int>& result)
{
    if (num.size() == 0 || || kth <= 0 || kth > num.size()) {
        throw std::invalid_argument("invalid_argument");
    }

    std::nth_element(num.begin(), num.begin() + kth - 1, num.end());
    for (int i = 0; i < kth; ++i) {
        result.at(i) = num.at(i);
    }
}

自己实现 nth_element:

一次划分:

int Partition(std::vector<int>& num, int first, int last)
{
    if (first < 0 || last >= num.size() || first > last) {
        throw std::invalid_argument("invalid_argument");
    }

    int index = rand() % (last - first + 1) + first;
    int key = num.at(index);
    std::swap(num.at(first), key);
    int midIndex = first;
    for (int i = first + 1; i <= last; ++i) {
        if (num.at(i) < num.at(first)) {
            std::swap(num.at(++midIndex), num.at(i));
        }
    }
    std::swap(num.at(midIndex), num.at(first));
    return midIndex;
}
void GetKth(std::vector<int>& num, int kth, std::vector<int>& result)
{
    if (num.size() == 0 || kth > num.size()) {
        throw std::invalid_argument("invalid_argument");
    }

    int first = 0;
    int last = num.size() - 1;
    int index = Partition(num, first, last);
    while (index != kth - 1) {
        if (index > kth - 1) {
            last = index - 1;
            index = Partition(num, first, last);
        } else {
            first = index + 1;
            index = Partition(num, first, last);
        }
    }
    for (int i = 0; i < kth; ++i) {
        result.at(i) = num.at(i);
    }
}

方法三:O(nlgk)解法

先创建一个大小为K的数据容器来存储最小的k个数,然后顺序遍历原数组:

如果容器中已有数字少于K个,则直接将原数组元素读入到容器中

如果容器中已有K个数字,表示已满,这时 我们找出容器中K个数的最大值,将这个最大值与待插入元素比较:

如果待插入元素比最大值还要大,那么直接忽略

如果待插入元素比最大值要小,那么就用这个元素替换掉最大值

我们该选取什么数据结构来表示这个数据容器呢?

如果是vector数组,那么每次找最大值需要O(k)时间,总时间为O(nk)

注意到我们每次仅仅需要的是最大值,很自然地想到最大堆,每次调整堆需要O(lgk),总时间为O(nlgk)

(1) 数据容器使用vector

void GetKth(const std::vector<int>& num, int kth, std::vector<int>& result)
{
    if (num.size() == 0 || kth <= 0 || kth > num.size()) {
        throw std::invalid_argument("invalid_argument");
    }

    for (int i = 0; i < num.size(); ++i) {
        if (result.size() < kth) {
            result.push_back(num.at(i));
        } else {
            auto maxIter = std::max_element(result.begin(), result.end());
            if (num.at(i) < *maxIter) {
                *maxIter = num.at(i);
            }
        }
    }
}

(2)数据容器使用 堆

首先 堆是一棵完全二叉树,根据完全二叉树的性质,我们可以非常方便的由根节点定位到左子节点和右子节点

一般使用数组来表示完全二叉树,结点之间的关系依靠 结点编号来维持

假设父节点的编号为 index (1<= index <= num.size() ) 其左子节点编号为 2*index, 其右子节点编号为 2*index + 1

(注:图片来自于:http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html)

(1) 堆调整

堆调整操作是对编号为index的结点进行“下降”,使以index为根的子树成为最大堆

(注:图片来自于:http://www.cnblogs.com/Anker/archive/2013/01/23/2873422.html)

void MaxHeapAdjust(std::vector<int>& num, int index) {
    int leftIndex = 2 * index;
    int rightIndex = 2 * index + 1;
    int largest = 0;
    if (leftIndex <= num.size()) {
        if (num.at(leftIndex - 1) > num.at(index - 1)) {
            largest = leftIndex;
        } else {
            largest = index;
        }
    }

    if (rightIndex <= num.size()) {
        if (num.at(rightIndex - 1) > num.at(largest - 1)) {
            largest = rightIndex;
        }
    }
    if (largest == 0) return;
    if (largest != index) {
        std::swap(num.at(index - 1), num.at(largest - 1));
        MaxHeapAdjust(num, largest);
    }
}

(2) 建堆

建立最大堆的过程是自底向上地调用最大堆调整程序将一个数组A[1.....N]变成一个最大堆。将数组视为一颗完全二叉树,从其最后一个非叶子节点(n/2)开始进行堆调整

for (int i = num.size() / 2; i >= 1 ; --i) {
     MaxHeapAdjust(num, i);
}

堆调整和建堆操作都已经说清楚了,再回到本题中来

我们首先取K个数建堆,然后对其后的数字不断与堆顶元素(即最大值)做比较,如果比堆顶元素要小,即更新堆顶元素,并以堆顶元素为根结点进行堆调整操作

void GetKth(const std::vector<int>& num, int kth, std::vector<int>& heap) {
    if (num.size() == 0 || kth <= 0 || kth > num.size()) {
        throw std::invalid_argument("invalid_argument");
    }

    for (int i = 0; i < kth; ++i) {
        heap.push_back(num.at(i));
    }
    for (int i = heap.size() / 2; i >= 1 ; --i) {
        MaxHeapAdjust(heap, i);
    }

    for (int i = kth; i < num.size(); ++i) {
        if (num.at(i) < heap.at(0)) {
            heap.at(0) = num.at(i);
            MaxHeapAdjust(heap, 1);
        }
    }
}

最后上一张图总结一下两种方法的优缺点:

剑指offer (30) 最小的K个数,布布扣,bubuko.com

时间: 2024-08-06 03:41:59

剑指offer (30) 最小的K个数的相关文章

剑指OFFER之最小的K个数(九度OJ1371)

题目描述: 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 输入: 每个测试案例包括2行: 第一行为2个整数n,k(1<=n,k<=200000),表示数组的长度. 第二行包含n个整数,表示这n个数,数组中的数的范围是[0,1000 000 000]. 输出: 对应每个测试案例,输出最小的k个数,并按从小到大顺序打印. 样例输入: 8 4 4 5 1 6 2 7 3 8 样例输出: 1 2 3 4 解题思路: 我们通过

【剑指Offer】最小的K个数

题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4, 直接使用sort排序,然后返回前k个数: class Solution { public: vector<int> GetLeastNumbers_Solution(vector<int> input, int k) { sort(input.begin(), input.end()); vector<int> r; if(k>inp

剑指offer:最小的k个数

题目描述输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. # -*- coding: utf-8 -*- # @Time : 2019-07-08 21:09 # @Author : Jayce Wong # @ProjectName : job # @FileName : getLeastNumbers.py # @Blog : https://blog.51cto.com/jayce1111 # @Github : h

剑指offer[29]——最小的K个数

题目描述 输入n个整数,找出其中最小的K个数.例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,. 这道题目对js来讲应该是很简单了,js有自带的sort函数,我们将输入的数组进行排序之后,输出前k个数就是题目要求 的结果. function GetLeastNumbers_Solution(input, k) { if(k==0 || k>input.length){return [];} input = input.sort((a,b) => a-b);

【剑指offer】最小的k的数量

转载请注明出处:http://blog.csdn.net/ns_code/article/details/26966159 题目描写叙述: 输入n个整数,找出当中最小的K个数.比如输入4,5,1,6,2,7,3,8这8个数字.则最小的4个数字是1,2,3,4. 输入: 每一个測试案例包含2行: 第一行为2个整数n,k(1<=n,k<=200000),表示数组的长度. 第二行包含n个整数.表示这n个数,数组中的数的范围是[0,1000 000 000]. 输出: 相应每一个測试案例,输出最小的k

剑指offer 30.时间效率 连续子数组的最大和

题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学.今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决.但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止).给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1) 错误解法: public class FindG

剑指 Offer 题目汇总索引

剑指 Offer 总目录:(共50道大题) 1. 赋值运算符函数(或应说复制拷贝函数问题) 2. 实现 Singleton 模式 (C#) 3.二维数组中的查找 4.替换空格               时间:O(n) 空间:O(1) 5.从尾到头打印链表 6. 重建二叉树          && 二叉树的各种遍历(BFS,DFS,DLR,LDR,LRD) 7.用两个栈实现队列 8.旋转数组的最小数字 9.斐波那契数列第 n 项        时间O(lgn) 10.一个整数的二进制表示中

剑指 Offer 题目索引

剑指 Offer 总目录:(共50道大题) 1. 赋值运算符函数(或应说复制拷贝函数问题) 2. 实现 Singleton 模式 (C#) 3.二维数组中的查找 4.替换空格               时间:O(n) 空间:O(1) 5.从尾到头打印链表 6. 重建二叉树          && 二叉树的各种遍历(BFS,DFS,DLR,LDR,LRD) 7.用两个栈实现队列 8.旋转数组的最小数字 9.斐波那契数列第 n 项        时间O(lgn) 10.一个整数的二进制表示中

python面试题六: 剑指offer

面试题3 二维数组中的查找 LeetCode题目:二维数组中,每行从左到右递增,每列从上到下递增,给出一个数,判断它是否在数组中思路:从左下角或者右上角开始比较 def find_integer(matrix, num): """ :param matrix: [[]] :param num: int :return: bool """ if not matrix: return False rows, cols = len(matrix), l