Leetcode算法学习日志-215 Kth Largest Element in an Array

Leetcode 215 Kth Largest Element in an Array

题目原文

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.

For example,
Given [3,2,1,5,6,4] and k = 2, return 5.

题意分析

找到一个数组中第k大的元素,也就是将数组从大到小依次排列,第k个元素就是所求。自然可以想到对原数组直接进行排序,但由于不要求k个元素左右元素的顺序,所以对数组进行完全排序是不必要的。

解法分析

本题选用C++,用了以下三种方法:

  • 归并排序
  • 快速排序
  • 堆排序

归并排序

本题最容易想到的就是通过对原序列进行排序后,直接输出第k大的数,由于归并排序时间复杂度O(nlogn),所以这种方法的时间复杂度也是O(nlogn)。代码如下:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        vector<int> sortedArray=mergeSort(nums);
        int res=sortedArray[k-1];
        return res; 
    }
private:
    vector<int> mergeSort(vector<int>& nums){
        int n=nums.size();
        vector<int> res;
        if(n==1){
            res.push_back(nums[0]);
            return res;
        }
        int i=n/2;
        vector<int> lArray(nums.begin(),nums.begin()+i);
        vector<int> rArray(nums.begin()+i,nums.end());
        vector<int> lRes;
        vector<int> rRes;
        lRes=mergeSort(lArray);
        rRes=mergeSort(rArray);
        int l=0,r=0;
        while(1){
            if(lRes[l]>=rRes[r]){
                res.push_back(lRes[l]);
                l++;
                if(lRes.begin()+l==lRes.end()){
                    res.insert(res.end(),rRes.begin()+r,rRes.end());   
                    return res;
                    break;
                }
            }
            else{
                res.push_back(rRes[r]);
                r++;
                if(rRes.begin()+r==rRes.end()){
                    res.insert(res.end(),lRes.begin()+l,lRes.end());   
                    return res;
                    break;
                }   
            }
                
        }     
    }
};
归并排序不支持原址排序,需要额外的存储空间,而堆排序和快速排序可以原址操作。

快速排序

此处并没有用到完整的快速排序算法,而是利用了快速排序中的Partition方法,也就是选取序列中的一个数,将他作为主元,将大于和小于它的数分别放在它两侧,则此主元的位置即为完整排序后它的位置,而其他元素并没有完整排序,因而节省了运算时间。本题中,选择最后一个元素作为主元,得到它的次序为i,如果i=k,则说明它就是所求,如果i>k,则对重排后主元左侧序列递归调用Partition函数,如果i<k,则对重排后主元右侧序列递归调用Partition函数。C++代码如下:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int res;
        int n=nums.size();
        int left=0;
        int right=n-1;
        while(1){
            res=partition(nums,left,right);
            if(res==k-1){
                return nums[res];
                break;
            }
            else if(res<k-1){
                left=res+1;
                continue;
            }
            else{
                right=res-1;
                continue;
            }                            
        }
        return res; 
    }
private:
    int partition(vector<int> &array,int left,int right){
        int i=left-1,j=left,temp;
        int x=array[right];
        for(j=left;j<right;j++){
            if(array[j]>=x){
                i++;
                temp=array[i];
                array[i]=array[j];
                array[j]=temp;
            }
        }
        temp=array[i+1];
        array[i+1]=x;
        array[right]=temp;
        return i+1;
    }
};
Partition函数在平均情况下的复杂度与将序列分成大小相等的两半的复杂度相同,这一特点和快速排序一样,所以平均情况下递归式为T(n)=T(n/2)+O(n),复杂度为O(n),而在最坏情况下T(n)=T(n-1)+O(n),复杂度为O(n^2)。上述代码将Partition写到一个While循环中,这与将Partition写为递归式一样。注意这里可以实现原址操作,所以只需要在递归时输入子序列的左右序号即可,如果有定义新的vector,则同归递归后容易造成内存消耗过大。将Partition改成以下形式能提高效率:

 pair<int,int> partition(vector<int>& nums, int lo, int hi){
        int lt = lo+1, gt = hi, j = lo+1;
        while(j<=gt){
            if(nums[j]<nums[lo])       swap(nums[j++], nums[lt++]);
            else if(nums[j]==nums[lo]) ++j;
            else                       swap(nums[j], nums[gt--]);
        }
        swap(nums[lo], nums[lt-1]);
        return make_pair(lt-1,gt);
    }

在之前的Partition中,重排后和主元相同的元素不一定和主元紧挨,而上述方法在Partition过程中引入了j,用来找到和主元相同的元素,[It-1,gt]间就是和主元相同的元素,如果k在这个范围,则主元即为所求,如果不在,则左右的子序列长度也相对缩小,这样的处理使得在序列中有大量重复元素的情况下,Partition不用进行过多的无用操作。

堆排序

由于不用得到完全的排序,所以本题也可以采用堆排序。堆就是一个完全二叉树,对于最大堆,父节点一定大于等于儿子,所以根,或者说数组的第一个元素,就是最大元素。建堆后将第一个元素与最后一个元素交换,然后heapSize减一,从根开始进行heapify,维护最大堆得特性,再将根与第heapSize个元素交换,多次进行上述操作实现堆排序,可以看出,堆排序也是原址排序。本题需要得到第k大的元素,则上述取根操作只需进行k次。C++代码如下:

class Solution {
private: int heapSize;
public:
    int findKthLargest(vector<int>& nums, int k) {
        int i=1;
        heapSize=nums.size();
        makeMaxHeap(nums);
        while(i<=k){
            swap(nums[0],nums[heapSize-1]);
            heapSize--;
            i++;
            heapify(nums,1);
        }
        return nums[heapSize];             
    }
    void makeMaxHeap(vector<int> &nums){
        int i;
        int n=nums.size();
        for(i=n/2;i>=1;i--)
            heapify(nums,i);
    }
    void heapify(vector<int> &nums,int i){
        int largest;
        if((2*i<=heapSize)&&(nums[i-1]<=nums[2*i-1]))
            largest=2*i;
        else
            largest=i;
        if((2*i+1<=heapSize)&&(nums[largest-1]<=nums[2*i]))
            largest=2*i+1;
        if(largest!=i){
            swap(nums[i-1],nums[largest-1]);
            heapify(nums,largest);
        }
    }
};
其中heapify就是从第i个元素开始维护最大堆的性质,makeMaxHeap用于对一个数组进行建堆操作,由于对于一个有n个节点的完全二叉树,倒数第二层是满的,所以可以得到序号为n/2+1到n的元素都是叶节点,叶节点都是平凡最大堆,所以建堆从节点n/2开始,对节点对应的子树进行heapify操作,最终得到一个最大堆。建堆的时间复杂度为O(n),heapify的时间复杂度为O(logn),logn为树的高(具体定义见算法导论),所以此算法复杂度为kO(logn)。

此题可以用STL的multiset和priorityqueue来完成,multiset可以自动将关键字排序,优先队列会先将优先级最大(关键字最大)的元素pop出来。代码如下:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.end());
        for (int i = 0; i < k - 1; i++)
            pq.pop(); 
        return pq.top();
    }
}; 
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        multiset<int> mset;
        int n = nums.size();
        for (int i = 0; i < n; i++) { 
            mset.insert(nums[i]);
            if (mset.size() > k)
                mset.erase(mset.begin());
        }
        return *mset.begin();
    }
};

此题用到的C++知识点如下:

  • 在类定义里,heapSize定义为private,则它的各成员函数均能访问它,因为对象创建时,成员变量就被定义,所以对于任何成员函数,它相当于全局变量。只要是函数内部定义的都是局部的,如main函数中定义的变量,在main函数所调用的函数中是不可见的,除非传参。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值