数组中第K大元素

数组中第K大元素

在面试的时候遇到过这个问题,后来在leetcode上也遇到了这个问题,于是记录下来以便以后快速回忆。

题目有多种思路,全排序是比较直观的想法,然而最低的时间复杂度为O(nlgn),并且不符合该题目的初衷。

题目更多想问的是如何在不进行全排序的条件下找到数组中第K大的元素,个人认为比较被面试官中意的解答有两个(理应也有其他的..),如下:

构建最大堆

简单分析时间复杂度: 构建堆的时间O(n),k次取最大元素要k(lgn),最终的时间复杂度为O(n+klgn).

显然是比全排序要好一些,但是当时面试的时候我回答这个思路,面试官认为并不是一个好的思路,因为面试官认为构建最大堆更适合题目:求数组中前K大的元素(维护一个大小为K的最大堆).
然而我还是认为这是一个可以的思路…..恩,代码还是贴一下,以后就省事了..

   int heap_size;
   int parent(int idx) {
        return (idx - 1) >> 1; //求父节点
    }
   int left(int idx) {
        return (idx << 1) + 1;//左子树
    }
   int right(int idx) {
        return (idx << 1) + 2;//右子树
    }

   void max_heapify(vector<int>& nums, int idx) {
        int largest = idx;
        int l = left(idx), r = right(idx);
        if (l < heap_size && nums[l] > nums[largest]) largest = l;
        if (r < heap_size && nums[r] > nums[largest]) largest = r;
        if (largest != idx) {
            swap(nums[idx], nums[largest]);
            max_heapify(nums, largest);
        }
     }
    //构建最大堆,从非叶子节点开始操作,可证明时间复杂度是O(n)
    void build_max_heap(vector<int>& nums) {
        heap_size = nums.size();
        for (int i = (heap_size >> 1) - 1; i >= 0; i--)
            max_heapify(nums, i);
    }

    int findKthLargest(vector<int>& nums, int k) {
        build_max_heap(nums);
        for (int i = 0; i < k; i++) {
            swap(nums[0], nums[heap_size - 1]);
            heap_size--;
            max_heapify(nums, 0);
        }//不断交换末尾元素和最大元素,之后重新max_heapify
        return nums[heap_size];
    }

快速排序思路

该思想类似快速排序:

  1. 以某一个元素为pivot元素,将元素分为两个集合,一个集合元素比pivot小,另一个比pivot大。

  2. 若比pivot大的元素数目正好为k-1,那么pivot就是我们要找到元素;若比pivot大的元素为m(小于k), 那么就在比pivot小的集合里面找第(k-m)大的元素; 若是比pivot大的元素为m(大于k),那就继续在该集合里面找第k大的元素。

  3. 重复上面步骤,直到找到第k大的元素

代码如下:

int partition(vector<int>& nums, int i, int j)
    {//类似快速排序的分组
        if (i == j) return i;

        int pivot = nums[i];
        std::swap(nums[i], nums[j]);

        int i0 = i;
        for(int k = i; k < j; k ++)
        {
            if(nums[k] <= pivot)
            {
                std::swap(nums[k], nums[i0 ++]);
            }
        }
        std::swap(nums[i0], nums[j]);
        return i0;
    }

    int findKthEle(vector<int>& nums, int i, int j,int k)
    {
        int index = partition(nums,i,j);
        int length = j-index+1;
        if(length == k)
            return nums[index];
        else if(length > k)
            return findKthEle(nums,index+1,j,k);
        else if(length <k)
            return findKthEle(nums,i,index-1,k-length);
    }

    int findKthLargest(vector<int>& nums, int k) 
    {
        size_t len = nums.size();

        return findKthEle(nums,0,len-1,k);
    }

时间复杂度:
该算法的平均时间复杂度为O(N)(详细的推导过程看算法导论9.2节),最坏情况为N^2,即每次划分把数组变为为(n-1) 和1的两断。

同时算法导论上有关于O(n)的算法(9.3节),其思路是选择pivot元素时是通过选择多组元素中的中位数,根据这个元素来划分,能够得到最坏情况为线性时间的选择算法(具体推导我还真是吃不消)。

看了算法导论以及网上相关的博客,发现这是个经典的老题目了…然而我并没有研究的非常透彻…

发布了42 篇原创文章 · 获赞 32 · 访问量 8万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章