|
| 1 | +# 题目描述(中等难度) |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +找出数组中数量超过 `n/3` 的数字,`n` 是数组的长度。 |
| 6 | + |
| 7 | +# 解法一 |
| 8 | + |
| 9 | +题目要求是 `O(1)` 的空间复杂度,我们先用 `map` 写一下,看看对题意的理解对不对。 |
| 10 | + |
| 11 | +`map` 的话 `key` 存数字,`value` 存数字出现的个数。如果数字出现的次数等于了 `n/3 + 1` 就把它加到结果中。 |
| 12 | + |
| 13 | +```java |
| 14 | +public List<Integer> majorityElement(int[] nums) { |
| 15 | + int n = nums.length; |
| 16 | + HashMap<Integer, Integer> map = new HashMap<>(); |
| 17 | + List<Integer> res = new ArrayList<>(); |
| 18 | + for (int i = 0; i < n; i++) { |
| 19 | + int count = map.getOrDefault(nums[i], 0); |
| 20 | + //之前的数量已经是 n/3, 当前数量就是 n/3 + 1 了 |
| 21 | + if (count == n / 3) { |
| 22 | + res.add(nums[i]); |
| 23 | + } |
| 24 | + //可以提前结束 |
| 25 | + if (count == 2 * n / 3 || res.size() == 2) { |
| 26 | + return res; |
| 27 | + } |
| 28 | + map.put(nums[i], count + 1); |
| 29 | + |
| 30 | + } |
| 31 | + return res; |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +# 解法二 |
| 36 | + |
| 37 | +[169 题](https://leetcode.wang/leetcode-169-Majority-Element.html) 我们做过找出数组的中超过 `n/2` 数量的数字,其中介绍了摩尔投票法,这里的话可以改写一下,参考 [这里](https://leetcode.com/problems/majority-element-ii/discuss/63520/Boyer-Moore-Majority-Vote-algorithm-and-my-elaboration)。 |
| 38 | + |
| 39 | +首先看一下 [169 题](https://leetcode.wang/leetcode-169-Majority-Element.html) 我们是怎么做的。 |
| 40 | + |
| 41 | +> 我们假设这样一个场景,在一个游戏中,分了若干个队伍,有一个队伍的人数超过了半数。所有人的战力都相同,不同队伍的两个人遇到就是同归于尽,同一个队伍的人遇到当然互不伤害。 |
| 42 | +> |
| 43 | +> 这样经过充分时间的游戏后,最后的结果是确定的,一定是超过半数的那个队伍留在了最后。 |
| 44 | +> |
| 45 | +> 而对于这道题,我们只需要利用上边的思想,把数组的每个数都看做队伍编号,然后模拟游戏过程即可。 |
| 46 | +> |
| 47 | +> `group` 记录当前队伍的人数,`count` 记录当前队伍剩余的人数。如果当前队伍剩余人数为 `0`,记录下次遇到的人的所在队伍号。 |
| 48 | +
|
| 49 | +对于这道题的话,超过 `n/3` 的队伍可能有两个,首先我们用 `group1` 和 `group2` 记录这两个队伍,`count1` 和 `count2` 分别记录两个队伍的数量,然后遵循下边的游戏规则。 |
| 50 | + |
| 51 | +将数组中的每一个数字看成队伍编号。 |
| 52 | + |
| 53 | +`group1` 和 `group2` 首先初始化为不可能和当前数字相等的两个数,将这两个队伍看成同盟,它俩不互相伤害。 |
| 54 | + |
| 55 | +然后遍历数组中的其他数字,如果遇到的数字属于其中的一个队伍,就将当前队伍的数量加 `1`。 |
| 56 | + |
| 57 | +如果某个队伍的数量变成了 `0`,就把这个队伍编号更新为当前的数字。 |
| 58 | + |
| 59 | +否则的话,将两个队伍的数量都减 `1`。 |
| 60 | + |
| 61 | +```java |
| 62 | +public List<Integer> majorityElement(int[] nums) { |
| 63 | + int n = nums.length; |
| 64 | + long group1 = (long)Integer.MAX_VALUE + 1; |
| 65 | + int count1 = 0; |
| 66 | + long group2 = (long)Integer.MAX_VALUE + 1; |
| 67 | + int count2 = 0; |
| 68 | + for (int i = 0; i < n; i++) { |
| 69 | + if (nums[i] == group1) { |
| 70 | + count1++; |
| 71 | + } else if (nums[i] == group2) { |
| 72 | + count2++; |
| 73 | + } else if (count1 == 0) { |
| 74 | + group1 = nums[i]; |
| 75 | + count1 = 1; |
| 76 | + } else if (count2 == 0) { |
| 77 | + group2 = nums[i]; |
| 78 | + count2 = 1; |
| 79 | + } else { |
| 80 | + count1--; |
| 81 | + count2--; |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + //计算两个队伍的数量,因为可能只存在一个数字的数量超过了 n/3 |
| 86 | + count1 = 0; |
| 87 | + count2 = 0; |
| 88 | + for (int i = 0; i < n; i++) { |
| 89 | + if (nums[i] == group1) { |
| 90 | + count1++; |
| 91 | + } |
| 92 | + if (nums[i] == group2) { |
| 93 | + count2++; |
| 94 | + } |
| 95 | + } |
| 96 | + //只保存数量大于 n/3 的队伍 |
| 97 | + List<Integer> res = new ArrayList<>(); |
| 98 | + if (count1 > n / 3) { |
| 99 | + res.add((int) group1); |
| 100 | + } |
| 101 | + |
| 102 | + if (count2 > n / 3) { |
| 103 | + res.add((int) group2); |
| 104 | + } |
| 105 | + return res; |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +上边有个技巧就是先将 `group` 初始化为一个大于 `int` 最大值的 `long` 值,这样可以保证后边的 `if` 条件判断中,数组中一定不会有数字和 `group`相等,从而进入后边的更新队伍编号的分支中。除了用 `long` 值,我们还可以用包装对象 `Integer`,将 `group` 初始化为 `null` 可以达到同样的效果。 |
| 110 | + |
| 111 | +当然,不用上边的技巧也是可以的,我们可以先在 `nums` 里找到两个不同的值分别赋值给 `group1` 和 `group2` 中即可,只不过代码上不会有上边的简洁。 |
| 112 | + |
| 113 | +# 总 |
| 114 | + |
| 115 | +解法一算是通用的解法,解法二的话看起来比较容易,但如果只看上边的解析,然后自己写代码的话还是会遇到很多问题的,其中 `if` 分支的顺序很重要。 |
0 commit comments