LeetCode之路:169. Majority Element

来源:互联网 发布:erlang python 编辑:程序博客网 时间:2024/05/20 01:08

一、引言

这道题做出来还是比较简单的,方法非常多,甚至于 C++ 最高票答案贴出来了 6 种方法供读者慢慢研究。不过这篇博客不对 C++ 最高票答案进行详细分析,而是对最高票答案 (Java)进行分析,因为确实是一个非常烧脑的一个解题方法。

话不多说,直接看题吧:

Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.
You may assume that the array is non-empty and the majority element always exist in the array.

题目信息不多,简单翻译下:

给定一个长度为 n 的数组,请找出其中的主要元素。主要元素是一种在数组中至少出现了 ⌊ n/2 ⌋ 次的元素。
你可以假定数组非空并且一定存在主要元素。

这里需要解释下 ⌊ n/2 ⌋:这并非中括号,而是向下取整的意思,也就是说如果 n = 5,那么这个式子等于 2,因为 5 / 2 = 2.5,2.5 向下取整则为 2。

那么说白了,这道题就是要找到给定数组中出现次数大于 ⌊ n/2 ⌋ 的元素嘛。

二、unordered_map 先行:元素出现次数的统计

只要涉及到元素的出现次数的统计,那么映射是肯定需要的了,又考虑到这里不需要映射内元素的排序,这里选择使用 std::unordered_map。

那么,现在让我们整理下解题的思路:

  1. 首先,我们需要遍历整个数组,拿到所有元素的出现次数的映射“数字 - 出现次数”

  2. 然后,我们遍历这个映射关系,找到其中出现次数最大的即可(出现次数最大的必然是大于 n / 2 向下取整的)

根据这个思路,我写出了第一个版本的代码:

// my solution 1 , runtime = 19 msclass Solution1 {public:    int majorityElement(vector<int>& nums) {        unordered_map<int, int> appear_count;        int max = 0, result = 0;        for (auto i : nums) ++appear_count[i];        for (auto item : appear_count)            if (max < item.second) {                max = item.second;                result = item.first;            }        return result;    }};

代码逻辑非常简单,就是使用 max 记录最大出现次数,result 记录需要返回的值,appear_count 记录出现次数。

但是这份代码并没有用到或者说并没有完美的用到 ⌊ n/2 ⌋ 这个条件,甚至于说,我们都不需要将整个数组全部遍历,我们只要找到了一个元素重复了 ⌊ n/2 ⌋ 次,我们就可以直接返回这个元素了,都不需要再进行处理了。

那么,为了更好的使用到 ⌊ n/2 ⌋这个条件,我写出了第二个版本的代码:

// my solution 2 , runtime = 22 msclass Solution3 {public:    int majorityElement(vector<int>& nums) {        unordered_map<int, int> appear_count;        for (auto i : nums)            if (++appear_count[i] > nums.size() / 2)                return i;    }};

这份代码只有短短 4 行,非常简练:

  1. 首先,我还是定义了一个 unordered_map 用来统计出现次数

  2. 然后,我试图遍历整个数组,每次遍历都递增对应元素的计数值;当该元素的计数值超过了 nums.size() / 2 了,就返回当前的这个元素值(这里值得注意的是,C++ 里面的 int 类型的值相除,默认就是地板除法也就是去尾法,相当于就是向下取整,所以这里正好契合题意)

这一个方法不仅思路简单,就连代码也是非常优雅,就我自己的观点而言,这是我最喜欢的解答方案。

三、无语凝噎:最高票答案在搞什么鬼

然而,做出来了自认为的最优雅的答案并没有让我觉得满足,于是我点开了最高票答案。

然后我就懵了 T_T

most posts answer

这里我将其代码“翻译”成了 C++:

// most posts answer , runtime = 19 msclass Solution4 {public:    int majorityElement(vector<int>& nums) {        int majority = nums[0], count = 1;        for (int i = 1; i < nums.size(); ++i) {            if (count == 0) {                count++;                majority = nums[i];            } else if (majority == nums[i]) {                count++;            } else count--;        }        return majority;    }};

这份代码在搞什么鬼???

完全看不懂啊 :(

不过别急,让我们拿着一个 case 慢慢地看这个方法,或许就能看出来什么端倪了:

Input: [1, 1, 2, 3, 1]

让我们输入这个数组,然后跟着它的代码走一遍看看:

进入循环前的各变量初始值:

i 初始值 majority 初始值 count 初始值 1 nums[0] 1

循环过程中的各变量值变化:

当前循环次数 i 值 num[i] 值 majority 值 count 值 第 1 次 1 1 nums[1] 2 第 2 次 2 2 nums[1] 1 第 3 次 3 3 nums[1] 0 第 4 次 4 1 nums[4] 1

最后返回了 majority = nums[4] = 1。接下来让我们详细分析下这个过程,看看作者的逻辑到底是什么样的:

  1. 首先,我们关注一下作者的初始值:作者声明了两个变量,顾名思义,majority 必然是最后要返回的主要元素的值,count 为计数值,至于是什么计数,我们还要参考后面的代码逻辑才能定义

  2. 然后,作者以 i = 1 为初始值开始了 nums 数组的遍历:这里为什么要以 i = 1 开始遍历呢?很简单,因为作者已经处理了第 1 个元素了,并且将 majority 和 count 都相对于第 1 个元素进行了初始化(一方面也是处理当前数组只有一个元素的情况);之后,作者进行了三次判断,首先判断 count 是否为 0,然后判断 majority 是否等于当前的 nums[i],最后的情况(也就是 count !=0 && majority != nums[i])的时候只进行 count 的递减。那么这里作者究竟是什么意思呢?我们通过观察多次遍历的过程,最终发现 count 其实就是作者用来模拟 ⌊ n/2 ⌋ 这个条件的工具。为什么这么说呢?只要一个元素的出现次数大于了 ⌊ n/2 ⌋ ,那么我们进行只要与此元素值相同的值进行 count ++操作,只要与此元素不同的值进行 count – 操作,最后的 count 结果必然大于 0。根据这个思路,作者循环遍历得到了 majority 的值

  3. 最后,作者放心的返回得到的 majority 的值,因为有对于第一个元素的初始化,所以当数组中只有一个元素的时候,也不会出错

如果还不懂上面的这个逻辑的话,我这里再详细进行解释下:

在一个数组中,一个出现了 至少 ⌊ n/2 ⌋ 次的元素有什么特征呢?

如果我们对 majority (该数组的主要元素)进行这样的操作:

1.当 majority = nums[i]count++
2.当 majority != nums[i]count--

当我们遍历完整个数组,必然出现 count > 0 对不对?
换句话说,当出现 count <= 0,当前的元素则必然不是 majority(该数组的主要元素)。

当我们遇到了 count = 0 的时候怎么办?我们记录当前的nums[i] 为 majority 再进行上述的计算即可。

相信根据我上述的两个表格和相信解释,大家应该就能了解作者的思路了。

可以说,非常非常奇妙!

但是,非常非常难以理解!

所以说,这样的代码其实是不可取的 T_T
不过呢,我们需要有这种能够读懂他人代码逻辑的能力,尽管他的代码再如何如何不友好,这是一种能力,一种每个程序员都应该掌握的能力。

四、总结

这道题,做出来也许只花了不到 15 分钟,但是为了看懂最高票答案的逻辑,我居然也差不多花了相等量的时间。

不过这一切都是值得的。

阅读代码也是一种能力,一种直接与代码的思维直接对话的能力。

其实我相信每个程序员都会有所感触:

真正写代码的时间并不多,多的是去理解他人的代码。这些代码或简洁或复杂,或注释良好或逻辑潦草,我们能做的并非是督促作者如何如何,我们需要的是提高自己的阅读能力,才能立于更加主动的位置。

认识到阅读代码与编写代码同样重要,那么这道题也算是很有收获了 ^_^

原创粉丝点击