力扣-414. 第三大的数

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 = &num;
            } else if(( b == nullptr || num > *b ) && num < *a){
                c = b;
                b = &num;
            } 
            else if(( c == nullptr || num > *c ) && b != nullptr && num < *b){ // 存在一种b==nullptr,但是num == *a的情况,这样会走到第三个if,但是b==nullptr导致空指针异常,所以加上判断
                c = &num;
            }
        }
        return c == nullptr ? *a : *c;
    }
};
posted @ 2024-04-23 23:00  DawnTraveler  阅读(18)  评论(0编辑  收藏  举报