返回数组中第n大的数

题目描述

给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。

示例

输入: [3, 2, 1]
输出: 1
解释: 第三大的数是 1

输入[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2

输入:[2, 2, 3, 1]
输出:1
解释: 注意,要求返回第三大的数,是指第三大且唯一出现的数。
     存在两个值为2的数,它们都排第二

心路历程

这是博主在面试腾讯的直面题,在看见leetcode的这道题时间复杂度可以是θ(n)的时候,我知道自己挂了,然后去问HR,还真给挂了。因为当时自己采用的是快速排序的方法。而且因为是博主第一次面试,比较紧张,自己手撸代码的能力也不强,所以想想挂了也是应该的
然后自己重新整理思路想到,自己在做项目的时候的set函数,那么我百度了一下为什么不能用C++的set函数呢?返回数组中第n大的数,就维护一个第n大的set,然后返回它最后一个元素即可。

代码

map, set, multimap, and multiset
上述四种容器采用红黑树实现,红黑树是平衡二叉树的一种。不同操作的时间复杂度近似为:
插入: O(logN)
查看:O(logN)
删除:O(logN)

hash_map, hash_set, hash_multimap, and hash_multiset
上述四种容器采用哈希表实现,不同操作的时间复杂度为:
插入:O(1),最坏情况O(N)。
查看:O(1),最坏情况O(N)。
删除:O(1),最坏情况O(N)。

rbegin() 返回指向集合中最后一个元素的反向迭代器
rend() 返回指向集合中第一个元素的反向迭代器

// An highlighted block
class Solution {
public:
    int thirdMax(vector<int>& nums){
        set<int> tempSet;
        for(int i=0; i<nums.size(); i++){
            tempSet.insert(nums[i]);
            if(tempSet.size()>3)
                tempSet.erase(tempSet.begin());
        }
        if(tempSet.size()<3)
            return *(tempSet.rbegin());
        else
            return *(tempSet.begin());
    }
};

排序算法

因为之前已经提到了排序算法,今天bz就把自己本科学到的比较经典的排序算法再实现一遍

基本排序(从小到大)

基本三部曲,也就是冒泡排序、选择排序以及插入排序
由于过于基础这里就实现插入排序进行实现

void insertInsort(vector<int>& nums){
    if(nums.size()<=1)
        return;
    for(int i=1; i<nums.size(); i++){
        for(int j=i-1; j>=0; j--){
            if (nums[j + 1] >= nums[j]) break;
            else swap(nums[j + 1], nums[j]);
        }
    }
}

计数排序&&基数排序

这是比较进阶的排序方法
在这里实现一个基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。

int maxbit(int data[], int n){ //辅助函数,求数据的最大位数
    int d = 1; //保存最大的位数
    int p = 10;
    for(int i = 0; i < n; ++i){
        while(data[i] >= p){
            p *= 10;
            ++d;
        }
    }
    return d;
}
void radixsort(int data[], int n){ //基数排序
    int d = maxbit(data, n);
    int *tmp = new int[n];  // 临时存放的数组
    int *count = new int[10]; //计数器
    int i, j, k;
    int radix = 1;
    for(i = 1; i <= d; i++){ //进行d次排序
        for(j = 0; j < 10; j++)
            count[j] = 0; //每次分配前清空计数器
        for(j = 0; j < n; j++){
            k = (data[j] / radix) % 10; //统计每个桶中的记录数
            count[k]++;
        }
        for(j = 1; j < 10; j++)
        	//将tmp中的位置依次分配给每个桶
            count[j] = count[j - 1] + count[j]; 
        for(j = n - 1; j >= 0; j--){ // 收尾排序
        	//将所有桶中记录依次收集到tmp中
            k = (data[j] / radix) % 10;
            tmp[count[k] - 1] = data[j];
            count[k]--;
        }
        for(j = 0; j < n; j++) //将临时数组的内容复制到data中
            data[j] = tmp[j];
        radix = radix * 10;
    }
    delete[]tmp;
    delete[]count;
}

最小(大)堆

最小堆,是一种经过排序的完全二叉树,
其中任一非终端节点的数据值均不大于其左子节点和右子节点的值
有兴趣的读者可以自己去实现,这里bz采用#include 的操作进行实现

	// 这里的nums是个vector
	// 构建最大堆
    make_heap(nums.begin(), nums.end(), less<int>());
    // 构建最小堆
    make_heap(nums.begin(), nums.end(), greater<int>());
    /*
    	其中还有一些关于堆的相关操作
    	push_heap()添加元素到堆	
    	pop_heap()把堆顶元素取出来
    	sort_heap()对整个堆排序
    */

快速排序

算法流程:
1)首先设定一个key(一般选择数组左边第一个)
2)再设置两个哨兵,分别从后往前找到比key小的数,从前往后找到比key大的数。然后二者进行交换,直到两个哨兵相遇
3)然后,将key与此使相遇位置的两个数进行交换
4)再将相遇位置左右边部分进行递归,即可

void quickSort(vector<int>& nums,int left, int right){
    if(left >= right) return;
    // 设置两个哨兵
    int i = left;
    int j = right;
    while(true){
    	// 这里必须先从右往左开始,不得调换顺序
        while(nums[j] >= nums[left] && j>i)
            j--;
        while(nums[i] <= nums[left] && i<j)
            i++;
        if (i >= j) break;
        swap(nums[i],nums[j]);
    }
    // 每次至少将这个元素移动到正确位置,这也是最差时间复杂度产生原因
    swap(nums[left],nums[j]);
    quickSort(nums,left,j-1);
    quickSort(nums,j+1,right);
}

2020_3_27

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值