摩尔投票法(力扣- -229. 求众数 II)

摩尔投票法(力扣- -229. 求众数 II)

一、题目描述

在这里插入图片描述

二、分析

  • 这道题如果用 O ( N ) O(N) O(N)的空间复杂度来解决是非常简单的,但是题目要求:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1)的算法解决此问题
  • 这里介绍摩尔投票法

摩尔投票法

  • 摩尔投票法,解决的问题是如何在任意多的候选人中,选出票数超过一半的那个人。注意,是超出一半票数的那个人。

  • 假设投票是这样的,[A, C, A, A, B](ABC 是指三个候选人)。

  • 第一张票与第二张票进行对坑,如果票不同则互相抵消掉

  • 接着第三票与第四票进行对坑,如果票相同,则增加这个候选人的可抵消票数

  • 这个候选人拿着可抵消票数与第五张票对坑,如果票不同,则互相抵消掉,即候选人的可抵消票数 -1

  • 图解

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

看完上面的图片之后,相信已经理解摩尔投票法是如何选取一个最有希望的候选人的

  • 猪猪猪猪猪猪意

  • 上面最后得道的结果并不意味着这个候选人的票数一定能超过一半,例如 [A, B, C] 的抵消阶段,最后得到的结果是 [C,1],C 候选人的票数也未能超过一半的票数。

  • 这里有一个优化,如果最后得到的可抵消票数是 0 的话,那他已经无缘票数能超过一半的那个人了。因为本来可能有希望的,但是被后面的一张不同的票抵消掉了。所以,在这里可以直接返回结果,无需后面的计算了。

  • 如果最后得到的抵消票数不为 0 的话,那说明他可能希望的,这是我们需要一个阶段来验证这个候选人的票数是否超过一半—— 计数阶段。

  • 所以摩尔投票法分为两个阶段:抵消阶段和计数阶段

  • 抵消阶段:两个不同投票进行对坑,并且同时抵消掉各一张票,如果两个投票相同,则累加可抵消的次数;

  • 计数阶段:在抵消阶段最后得到的抵消计数只要不为 0,那这个候选人是有可能超过一半的票数的,为了验证,则需要遍历一次,统计票数,才可确定

  • 摩尔投票法经历两个阶段最多消耗 O ( 2 n ) O(2n) O(2n) 的时间,也属于 O ( n ) O(n) O(n) 的线性时间复杂度,另外空间复杂度也达到常量级。

  • 理解摩尔投票法之后,我们再回到题目描述,题目可以看作是:在任意多的候选人中,选出票数超过⌊ 1/3 ⌋的候选人

  • 我们可以这样理解,假设投票是这样的 [A, B, C, A, A, B, C](ABC 是指三个候选人)。

  • 第 1 张票,第 2 张票和第3张票进行对坑,如果票都不同,则互相抵消掉;

  • 第 4 张票,第 5 张票和第 6 张票进行对坑,如果有部分相同,则累计增加他们的可抵消票数,如 [A, 2] 和 [B, 1];

  • 接着将 [A, 2] 和 [B, 1] 与第 7 张票进行对坑,如果票都没匹配上,则互相抵消掉,变成 [A, 1] 和 `[B, 0] 。

  • 图解

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

  • 在这里插入图片描述

总结

  • 看完图片之后,是不是理解了,是不是也清晰了。

  • 如果至多选一个代表,那他的票数至少要超过一半 ( ⌊ 1 / 2 ⌋ ) (⌊ 1/2 ⌋) 1/2的票数;

  • 如果至多选两个代表,那他们的票数至少要超过 ( ⌊ 1 / 3 ⌋ ) (⌊ 1/3 ⌋) (1/3) 的票数;

  • 如果至多选m个代表,那他们的票数至少要超过 ( ⌊ 1 / ( m + 1 ) ⌋ ) (⌊ 1/(m+1) ⌋) (1/(m+1))的票数。

所以以后碰到这样的问题,而且要求达到线性的时间复杂度以及常量级的空间复杂度,直接套上摩尔投票法

三、代码

class Solution {
public:
    vector<int> majorityElement(vector<int>& nums) {
        if(nums.empty())
        {
            return {};
        }

		//题目求的是满足元素个数大雨n / 3的元素,空间复杂度为O(1)
		//所以只需要按摩尔投票法记录2个元素的抵消个计数情况即可

		//第一个元素及票数情况
        int target1 = nums[0];
        int count1 = 0;

		//第二个元素及票数情况
        int target2 = nums[0];
        int count2 = 0;

		//遍历
        for(auto& e : nums)
        {
        	//如果和记录的元素中有相等的情况,更新他的计数
            if(e == target1)
            {
                count1++;
                continue;
            }
            else if(e == target2)
            {
                count2++;
                continue;
            }

			//程序走到这里代表没有和记录的元素相等的情况,代表
			//需要进行抵消操作

			//在进行抵消之前需要判断记录的元素是否有为0情况,因为如果为0
			//需要更新记录的元素
            if(count1 == 0)
            {
                target1 = e;
                count1++;
                continue;
            }
            else if(count2 == 0)
            {
                target2 = e;
                count2++;
                continue;
            }

			//抵消
            count1--;
            count2--;
        }

		//再对有可能满足情况的标记元素进行一次计数,进行验证
        int val1 = 0;
        int val2 = 0;
        for(auto& e : nums)
        {
            if(e == target1)
            {
                val1++;
            }
            else if(e == target2)
            {
                val2++;
            }
        }

        vector<int> ret;
        if(val1 > nums.size() / 3)
        {
            ret.emplace_back(target1);
        }

        if(val2 > nums.size() / 3)
        {
            ret.emplace_back(target2);
        }

        return ret;
    }
};
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页