题目描述
给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是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