算法题 第三大的数

414. 第三大的数

问题描述

给你一个非空数组 nums,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。

注意:答案必须是整数,且需要考虑整数溢出问题。

示例

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

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

输入: nums = [2,2,3,1]
输出: 1
解释: 注意,要求的是第三大的数,1 是第三大的数。

算法思路

核心思想:维护前三大的数

  1. 关键:只需要跟踪最大的三个不同数值
  2. 去重处理:相同的数视为同一个排名
  3. 边界情况:如果不同数少于3个,返回最大值

方法一:一次遍历维护三个变量

  • 使用 firstsecondthird 分别记录第一、第二、第三大的数
  • 遍历数组,根据当前数更新这三个变量
  • 初始化为 Long.MIN_VALUE 避免整数溢出问题

方法二:使用有序集合(TreeSet)

  • 用 TreeSet 自动去重和排序
  • 保留最大的三个数
  • 最后判断集合大小

代码实现

方法一:一次遍历(推荐)

class Solution {
    /**
     * 找到数组中第三大的数,如果不存在则返回最大数
     * 
     * @param nums 非空整数数组
     * @return 第三大的数,如果不存在则返回最大数
     */
    public int thirdMax(int[] nums) {
        // 使用Long.MIN_VALUE作为初始值,避免与Integer.MIN_VALUE混淆
        // 因为Integer.MIN_VALUE可能是有效数据
        long first = Long.MIN_VALUE;   // 最大值
        long second = Long.MIN_VALUE;  // 第二大值
        long third = Long.MIN_VALUE;   // 第三大值
        
        // 遍历数组中的每个数
        for (int num : nums) {
            // 如果当前数已经存在于前三名中,跳过(去重)
            if (num == first || num == second || num == third) {
                continue;
            }
            
            // 更新前三大的数
            if (num > first) {
                // 当前数比最大值还大
                third = second;   // 原第二大变成第三大
                second = first;   // 原最大变成第二大
                first = num;      // 当前数成为最大
            } else if (num > second) {
                // 当前数介于最大和第二大之间
                third = second;   // 原第二大变成第三大
                second = num;     // 当前数成为第二大
            } else if (num > third) {
                // 当前数介于第二大和第三大之间
                third = num;      // 当前数成为第三大
            }
            // 如果num <= third,不需要更新
        }
        
        // 检查是否存在第三大的数
        // 如果third仍然是初始值,说明不同数少于3个
        if (third == Long.MIN_VALUE) {
            return (int)first;  // 返回最大值
        } else {
            return (int)third;  // 返回第三大的数
        }
    }
}

方法二:使用TreeSet

import java.util.TreeSet;

class Solution {
    /**
     * 使用TreeSet的解法
     * 
     * @param nums 非空整数数组
     * @return 第三大的数或最大数
     */
    public int thirdMax(int[] nums) {
        // 使用TreeSet自动去重和排序(降序)
        TreeSet<Integer> set = new TreeSet<>((a, b) -> b - a);
        
        // 添加所有元素(自动去重)
        for (int num : nums) {
            set.add(num);
            
            // 优化:只保留最大的3个数
            if (set.size() > 3) {
                set.pollLast();  // 移除最小的元素
            }
        }
        
        // 如果少于3个不同数,返回最大值
        if (set.size() < 3) {
            return set.first();
        } else {
            // 获取第三大的数(从大到小的第3个)
            int count = 0;
            for (int num : set) {
                count++;
                if (count == 3) {
                    return num;
                }
            }
            return set.first(); // 理论上不会执行到这里
        }
    }
}

算法分析

  • 时间复杂度

    • 方法一:O(n) - 一次遍历
    • 方法二:O(n log 3) ≈ O(n) - TreeSet操作最多3个元素
  • 空间复杂度

    • 方法一:O(1) - 只使用三个变量
    • 方法二:O(1) - TreeSet最多存储3个元素
  • 方法对比

    • 方法一:效率最高,空间最优,推荐使用
    • 方法二:代码简洁,但常数因子较大

算法过程

nums = [2,2,3,1]

初始化

  • first = Long.MIN_VALUE
  • second = Long.MIN_VALUE
  • third = Long.MIN_VALUE

遍历过程

  1. num = 2
    • 2 > Long.MIN_VALUEfirst=2, second=MIN, third=MIN
  2. num = 2(重复)
    • 跳过(已存在)
  3. num = 3
    • 3 > 2third=MIN, second=2, first=3
  4. num = 1
    • 1 < 31 < 21 > MINthird=1

最终状态

  • first=3, second=2, third=1
  • third ≠ MIN_VALUE → 返回 1

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准情况
    int[] nums1 = {3,2,1};
    System.out.println("Test 1: " + solution.thirdMax(nums1)); // 1
    
    // 测试用例2:少于3个不同数
    int[] nums2 = {1,2};
    System.out.println("Test 2: " + solution.thirdMax(nums2)); // 2
    
    // 测试用例3:有重复元素
    int[] nums3 = {2,2,3,1};
    System.out.println("Test 3: " + solution.thirdMax(nums3)); // 1
    
    // 测试用例4:全相同元素
    int[] nums4 = {1,1,1};
    System.out.println("Test 4: " + solution.thirdMax(nums4)); // 1
    
    // 测试用例5:包含Integer.MIN_VALUE
    int[] nums5 = {-3, -2, -1, Integer.MIN_VALUE};
    System.out.println("Test 5: " + solution.thirdMax(nums5)); // -3
    // first=-1, second=-2, third=-3
    
    // 测试用例6:大数
    int[] nums6 = {1,2,3,4,5};
    System.out.println("Test 6: " + solution.thirdMax(nums6)); // 3
    
    // 测试用例7:负数
    int[] nums7 = {-1,-2,-3};
    System.out.println("Test 7: " + solution.thirdMax(nums7)); // -3
}

关键点

  1. 去重处理

    • 相同的数视为同一个排名
    • 需要在更新前检查是否已存在
  2. 整数溢出问题

    • 不能用 Integer.MIN_VALUE 作为初始值
    • 因为 Integer.MIN_VALUE 可能是有效数据
    • 使用 Long.MIN_VALUE 避免混淆
  3. 更新顺序

    • 必须从大到小判断
    • 先检查是否大于 first,再 second,最后 third
  4. 边界情况

    • 数组长度为1
    • 所有元素相同
    • 只有两种不同元素

常见问题

  1. 为什么用long而不是int?

    • 因为需要一个比所有int都小的初始值
    • Long.MIN_VALUE < Integer.MIN_VALUE
    • 这样 Long.MIN_VALUE 不会与任何有效int值冲突
  2. 如果用Integer.MIN_VALUE作为初始值会怎样?

    // 错误示例
    int first = Integer.MIN_VALUE;
    
    • 当数组包含 Integer.MIN_VALUE 时,无法区分是初始值还是有效值
    • 会导致错误判断是否存在第三大的数
  3. 能否用排序解决?

    Arrays.sort(nums);
    // 去重后找第三大的数
    
    • 时间复杂度O(n log n),不如一次遍历高效
  4. 算法的直观理解

    • 想象有三个奖杯:金、银、铜
    • 遍历每个参赛者,根据成绩更新奖杯
    • 成绩最好的拿金牌,次好的拿银牌,第三好的拿铜牌
    • 最后如果铜牌有人拿,就返回铜牌成绩;否则返回金牌成绩
  5. 为什么更新时要按顺序?

    • 必须先检查最大值,否则会错误更新
    • 例如,如果先检查 num > third,那么 first 的更新会出错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值