1.题目
题目地址(414. 第三大的数 - 力扣(LeetCode))
https://leetcode.cn/problems/third-maximum-number/
题目描述
给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。
示例 1:
输入:[3, 2, 1] 输出:1 解释:第三大的数是 1 。
示例 2:
输入:[1, 2] 输出:2 解释:第三大的数不存在, 所以返回最大的数 2 。
示例 3:
输入:[2, 2, 3, 1] 输出:1 解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。 此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。
提示:
1 <= nums.length <= 104
-231 <= nums[i] <= 231 - 1
进阶:你能设计一个时间复杂度 O(n)
的解决方案吗?
2.题解
2.1 排序(nlog(n))
思路
先排序,然后寻找是否有第三大的数,有则返回,没有则返回最大值
这里很有意思if(nums[i] != nums[i-1] && ++diff == 3), 判断条件使用了逻辑熔断,&&中 前者不成立,后面的语句便不会执行
也就是说,只有两个数不同时, 才会执行++diff, 避免了重复加的问题
代码
- 语言支持:C++
C++ Code:
class Solution {
public:
int thirdMax(vector<int>& nums) {
sort(nums.begin(), nums.end(), greater<>()); // 默认排序是less<>()
for(int i = 1, diff = 1; i < nums.size(); i++){
if(nums[i] != nums[i-1] && ++diff == 3){ // 跳过了第一个元素,方便比较前后值
return nums[i];
}
}
return nums[0];
}
};
复杂度分析
令 n 为数组长度。均为排序所需
- 时间复杂度:\(O(nlogn)\)
- 空间复杂度:\(O(logn)\)
2.2 Set集合
思路
思路很简单,利用set集合自动排序的功能,维持个数总是在3个较大值,输出最大数就是排头, 输出最小值就是排尾
这里set集合默认是less排序,也就是从小到大,我尝试了一下使用greater(变麻烦了)
尤其是在处理erase最后一个最小的元素时, 由于st.end()并不是指向最后一个元素, 而且erase似乎不接受st.rbegin(begin和end都是Iterator类型, rbegin和rend都是 reverse_iterator类型)
好在我们有一个函数prev, 可以求取指定迭代器的上一个迭代器
代码
class Solution {
public:
int thirdMax(vector<int>& nums) {
set<int, greater<int>> st;
for(int num: nums){
st.emplace(num);
if(st.size() > 3){
st.erase(prev(st.end()));
}
}
return st.size()<3?*st.begin():*st.rbegin();
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(1)\)
2.3 一次循环
思路
使用三个记录变量, 遍历一次数组, 不断更新最大的三个值, 最后判断结果即可
这里有个问题, 由于数据 -2^31 <= nums[i] <= 2^31 - 1
我们这里如果设置初始值为 INT_MIN 的话, 出现类似[1,2,-2147483648(INT_MIN)]的数据, 就会有c==INT_MIN 导致输出最大值发生错误
所以我们选择使用 LONG_MIN = -2147483648 和 long类型(这里两者不是相等吗?为何就可以了?) // TODO 不是很清楚为何
代码
class Solution {
public:
int thirdMax(vector<int>& nums) {
long a = LONG_MIN, b = LONG_MIN, c = LONG_MIN;
for(long num : nums){
if(num > a){
c = b;
b = a;
a = num;
} else if(num > b && num < a){
c = b;
b = num;
} //else if(num > c && num != b){ 这里我们会下意识认为之前不是判断 num > b 不成立了吗, 这里肯定是 num <= b, 我们只要判断 num != b 就行了, 但是注意上面也有可能是不满足num < a 而下来的,如此判断便会出错
else if(num > c && num < b){
c = num;
}
}
return c == LONG_MIN ? a : c;
}
};
复杂度分析
令 n 为数组长度。
- 时间复杂度:\(O(n)\)
- 空间复杂度:\(O(1)\)
2.4 一次循环(指针写法-无需考虑范围)
思路
既然使用int之流需要考虑范围,我们考虑使用指针,无穷小就用空指针来表示
注意:
1.这里必须使用&num, 填写num的真实地址, 而不是复制的地址, 因为在循环结束之后就会被释放,这是不可以的
2.使用熔断机制, 判断值为无穷小(nullptr) 还是有值 (空指针放前面,如果是则熔断,不会执行后面的a,b,*c导致空指针异常)
3.对于c的判断条件, 要小心这里仍然可能出现 b == nullptr的情况, 也就是 第二个if中, b==nullptr, 而 num == *a, 导致跳过(重复元素)
代码
class Solution {
public:
int thirdMax(vector<int>& nums) {
int *a = nullptr, *b = nullptr, *c = nullptr;
for(int &num : nums){
if(a == nullptr || num > *a ){
c = b;
b = a;
a = #
} else if(( b == nullptr || num > *b ) && num < *a){
c = b;
b = #
}
else if(( c == nullptr || num > *c ) && b != nullptr && num < *b){ // 存在一种b==nullptr,但是num == *a的情况,这样会走到第三个if,但是b==nullptr导致空指针异常,所以加上判断
c = #
}
}
return c == nullptr ? *a : *c;
}
};
标签:414,nums,int,复杂度,nullptr,第三,力扣,num,&&
From: https://www.cnblogs.com/trmbh12/p/18154044