229. Majority Element II

Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times. The algorithm should run in linear time and in O(1) space.

这题跟之前那道找绝对众数的题,也就是找唯一存在的过半重复的数,是一个套路。但是这题难是因为要求在线性时间内,还不能用多余内存,所以排序和哈希就没戏了。就要用上题引入的摩尔投票法。

方法一:摩尔投票法
vector<int> majorityElement(vector<int>& nums) {
    vector<int> res;
    int m = 0, n = 0, cm = 0, cn = 0;
    for (auto &a : nums) {
        if (a == m) cm++;
        else if (a == n) cn++;
        else if (cm == 0) m = a, cm++;
        else if (cn == 0) n = a, cn++;
        else cm--, cn--;
    }
    cm = 0, cn = 0;
    //验证m和n是否是符合条件的众数
    for (auto &a : nums) {
        if (a == m) cm++;
        else if (a == n) cn++;
    }
    if (cm > nums.size()/3) res.push_back(m);
    if (cn > nums.size()/3) res.push_back(n);
    return res;
}

解释一下,首先m和n代表我们对可能出现的两个众数的初始值。这一题没有规定众数出现的可能性,也就是说最多可能有两个满足条件的众数,也可能只有一个,甚至没有。我们要保证如果有,不管一个还是两个都会在我们的m和n里面。我们分三类讨论我们的算法正确性。
1. 如果有两个众数。那么他们都占有三分之一以上的存在,同样,我们把除此之外的称为杂项。那么m和n都只是在跟杂项比,因为我们可以看到在循环里,杂项都是分别跟他俩在比较。这就相当于两个之前那种绝对众数的问题,因为抛开n,m一定是剩下里面过半的存在。这是我们首先明确的一个基本大小关系。那么如果我们的m和n找对了,那就是大家在跟杂项一一抵消,最后还能剩下的一定就是众数。如果没有找对,那就是杂项之间相互抵消,直到找对为止,反正不管怎么样,最后剩下的一定是正确的众数。
2. 如果只有一个众数m,那么n就是在不停的做杂项的内部消耗。因为我们在判断不一样的时候是保证同时跟m和n不一样,也就是n为0。也就是一个杂项要想消除一个m,那么它要么会成为另一个n候选,要么会同时消除一个m和另一个自己的杂项同类。所以存活到最后的一定有数量占优势的m。
3. 如果一个众数都没有,没有关系,最后产生的m和n会在验证中被排除。所以标准的摩尔投票需要两次线性扫描,因为验证的关系。

方法二:快选排序法

在很多要求线性求解的算法里,快排快选这种思想都可以借鉴,因为完整的递归完成排序确实需要O(nlogn)。但是很多问题并不需要完整排序的结果,我么只用借鉴排序的过程,有的时候每次递归只用看分类后的一部分数组,比如这题,然后整个复杂度就降为线性了。
我们选一个pivot后对数组排序,分为小于等于和大于三个部分,然后检查三个部分的长度,等于部分如果达到n/3以上就添加到结果,小于和大于如果长度满足就递归检查,但每次递归一定会至少舍弃一个部分,所以每次减少三分之一的长度下递归,复杂度降为线性。
代码:

vector<int> majorityElement(vector<int>& nums) {
    vector<int> res;
    split(nums, 0, nums.size() - 1, nums.size()/3, res);
    //将左右边界,判断长度,结果数组传递
    return res;
}
void split(vector<int> &nums, int left, int right, int len, vector<int> &res) {
    if (left > right) return;
    int i = left + 1, j = left, k = left;
    //i线性扫描,j是等于部分的左端,k是右端
    int pivot = nums[left];
    while (i <= right) {
        if (nums[i] == pivot) swap(nums[i++], nums[++k]);
        else if (nums[i] > pivot) i++;
        else {
            swap(nums[j++], nums[i]);
            swap(nums[++k], nums[i++]);
        }
    }
    if (k - j + 1 > len) res.push_back(pivot);
    if (j - left > len) split(nums, left, j - 1, len, res);
    if (right - k > len) split(nums, k + 1, right, len, res);
}

这种做法相比上一种摩尔投票法其实更好理解和想到,虽然可能效率不如上一个,可是在可读性和可移植性上更优,而且不容易出错。即使改为1/4, 1/5也是可以这么来。总之这种不完全的快排快选思想很有用,大家可以注意下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值