基本分析
为了让解法更具有「通用性」,我们可以考虑添加一个额外条件:数组中不一定存在「出现次数超过一半的数字」,如果不存在的话返回 。
这也是在面试过程中,很容易被拓展的一个点。
哈希表
一个朴素的做法是使用哈希表进行计数,如果发现某个元素数量超过总数一半,说明找到了答案。
代码:
import java.util.*; public class Solution { public int MoreThanHalfNum_Solution(int [] nums) { int n = nums.length; Map<Integer, Integer> map = new HashMap<>(); for (int x : nums) { map.put(x, map.getOrDefault(x, 0) + 1); if (map.get(x) > n / 2) return x; } return -1; } }
- 时间复杂度:
- 空间复杂度:
摩尔投票
这还是道「摩尔投票」模板题。
摩尔投票 :在集合中寻找可能存在的多数元素,这一元素在输入的序列重复出现并占到了序列元素的一半以上;在第一遍遍历之后应该再进行一个遍历以统计第一次算法遍历的结果出现次数,确定其是否为众数;如果一个序列中没有占到多数的元素,那么第一次的结果就可能是无效的随机元素。
换句话说,每次将两个不同的元素进行「抵消」,如果最后有元素剩余,则「可能」为元素个数大于总数一半的那个。
具体的,我们定义一个变量 来保存那个可能为主要元素的值, 用来记录该值的出现次数。然后在遍历数组 过程中执行如下逻辑:
- 如果 为 :说明之前出现过的 已经被抵消完了,更新一下 为当前值,出现次数为 :
x = nums[i], cnt = 1
; - 如果 不为 :说明之前统计的 还没被抵消完,这是根据 与 是否相等进行计算即可:
cnt += nums[i] == x ? 1 : -1
。
当处理完 之后,我们得到了一个「可能」的主要元素。注意只是可能,因为我们在处理过程中只使用了 和 来记录,我们是无法确定最后剩下的 是经过多次抵消后剩余的主要元素,还是只是不存在主要元素的数组中的无效随机元素。
因此我们需要再进行一次遍历,检查这个「可能」的主要元素 的出现次数是否超过总数一半。
代码:
import java.util.*; public class Solution { public int MoreThanHalfNum_Solution(int [] nums) { int n = nums.length; int x = -1, cnt = 0; for (int i : nums) { if (cnt == 0) { x = i; cnt = 1; } else { cnt += x == i ? 1 : -1; } } cnt = 0; for (int i : nums) if (x == i) cnt++; return cnt > n / 2 ? x : -1; } }
- 时间复杂度:
- 空间复杂度:
最后
这是我们「剑指 の 精选」系列文章的第 No.28
篇,系列开始于 2021/07/01。
该系列会将牛客网「剑指 Offer」中比较经典而又不过时的题目都讲一遍。
在提供追求「证明」&「思路」的同时,提供最为简洁的代码。
欢迎关注,交个朋友 (`・ω・´)