算法题 多数元素 II

LeetCode 229. 多数元素 II

问题描述

给定一个大小为 n 的整数数组,找出所有出现次数 大于 ⌊n/3⌋ 的元素。要求时间复杂度 O(n),空间复杂度 O(1)。

示例

示例 1:

输入:nums = [3,2,3]
输出:[3]

示例 2:

输入:nums = [1]
输出:[1]

示例 3:

输入:nums = [1,2]
输出:[1,2]

算法思路

摩尔投票法扩展

  1. 核心思想:出现次数 > n/3 的元素最多有两个
  2. 维护两个候选元素
    • 遍历数组,用两个候选元素 candidate1, candidate2 和计数器 count1, count2 记录
    • 遇到相同元素:对应计数器+1
    • 遇到不同元素:两个计数器-1(抵消)
    • 计数器归零时:更新候选元素
  3. 验证阶段:重新统计候选元素的真实出现次数

代码实现

class Solution {
    public List<Integer> majorityElement(int[] nums) {
        List<Integer> result = new ArrayList<>();
        if (nums == null || nums.length == 0) return result;
        
        // 初始化两个候选元素和计数器
        int candidate1 = nums[0], candidate2 = nums[0];
        int count1 = 0, count2 = 0;
        
        // 第一轮遍历:找出可能的候选元素
        for (int num : nums) {
            // 更新候选元素1
            if (num == candidate1) {
                count1++;
            } 
            // 更新候选元素2
            else if (num == candidate2) {
                count2++;
            }
            // 替换候选元素1
            else if (count1 == 0) {
                candidate1 = num;
                count1 = 1;
            } 
            // 替换候选元素2
            else if (count2 == 0) {
                candidate2 = num;
                count2 = 1;
            }
            // 抵消操作
            else {
                count1--;
                count2--;
            }
        }
        
        // 第二轮遍历:验证候选元素
        count1 = 0;
        count2 = 0;
        for (int num : nums) {
            if (num == candidate1) count1++;
            else if (num == candidate2) count2++;
        }
        
        // 检查是否满足条件
        int n = nums.length;
        if (count1 > n / 3) result.add(candidate1);
        if (count2 > n / 3) result.add(candidate2);
        
        return result;
    }
}

算法分析

  • 时间复杂度O(n)
    两次独立遍历(候选选择 + 验证)
  • 空间复杂度O(1)
    仅使用常数空间存储候选元素和计数器

算法过程

nums=[1,1,1,3,3,2,2,2]

0: num=1 → cnt1==0 → cnt1=1
1: num=1 → cnt1=2
2: num=1 → cnt1=3
3: num=3 → 不属于c1/c2且cnt2=0 → c2=3, cnt2=1
4: num=3 → cnt2=2
5: num=2 → 不属于c1/c2 → 抵消:cnt1=2, cnt2=1
6: num=2 → 不属于c1/c2 → 抵消:cnt1=1, cnt2=0
7: num=2 → cnt2==0 → c2=2, cnt2=1
候选:c1=1, c2=2

验证:
  c1=1: 出现3次 > 2.67 → 有效
  c2=2: 出现3次 > 2.67 → 有效
结果:[1,2] 

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    int[] nums1 = {3,2,3};
    System.out.println("Test 1: " + solution.majorityElement(nums1)); // [3]
    
    // 测试用例2:两个多数元素
    int[] nums2 = {1,1,1,3,3,2,2,2};
    System.out.println("Test 2: " + solution.majorityElement(nums2)); // [1,2]
    
    // 测试用例3:单一候选
    int[] nums3 = {0,0,0,3,4,5};
    System.out.println("Test 3: " + solution.majorityElement(nums3)); // [0]
    
    // 测试用例4:无多数元素
    int[] nums4 = {1,2,3,4};
    System.out.println("Test 4: " + solution.majorityElement(nums4)); // []
    
    // 测试用例5:全相同元素
    int[] nums5 = {5,5,5,5};
    System.out.println("Test 5: " + solution.majorityElement(nums5)); // [5]
    
    // 测试用例6:三个候选但只有两个有效
    int[] nums6 = {2,2,1,3,1,3,2,3}; // [2,2,1,3,1,3,2,3] → 2:3, 3:3, 1:2 → n/3=2.67 → 2和3满足
    System.out.println("Test 6: " + solution.majorityElement(nums6)); // [2,3]
}

关键点

  1. 候选元素初始化

    • 初始计数器设为零
    • 候选元素值不重要(会被覆盖)
  2. 遍历顺序

    • 先检查是否匹配已有候选
    • 再检查计数器是否为零
    • 最后执行抵消操作
  3. 验证必要性

    • 摩尔投票法保证真多数元素一定在候选元素中
    • 但候选元素不一定是多数元素,必须验证
  4. 抵消逻辑

    • 每次抵消相当于移除三个不同的元素
    • 不影响最终多数元素的相对数量

常见问题

1. 为什么最后要重新验证?
抵消操作可能引入非多数元素作为候选(如 [1,2,3] 中候选可能是 3,但实际不满足条件)。

2. 处理元素全相同的情况?
算法能正确处理:第一个元素成为候选,后续元素增加计数,最终验证通过。

3. 最坏情况时间复杂度?
严格 O(n),两次线性遍历。

4. 为什么用 else if 链?
确保每个元素只触发一个操作(匹配、更新候选或抵消)。

5. 空数组处理?
开头添加检查:if (nums == null || nums.length == 0) return result;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值