多数元素
题目
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
- 示例 1:
输入:[3,2,3]
输出:3
- 示例 2:
输入:[2,2,1,1,1,2,2]
输出:2
进阶:
尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
思路0-HashMap 次数统计
这应该是最基本的思路。
我们利用 HashMap 统计每一个元素出现的次数,出现次数最多的,或者说超过 n/2 的,就是满足条件的结果:
java-出现次数最多的
这种解法,适应范围更加广泛一点,题目如果调整为出现次数最多的,也可以解决。
public int majorityElement(int[] nums) {
int result = nums[0];
int maxCount = 1;
Map<Integer, Integer> countMap = new HashMap<>(nums.length);
//O(N)
for(int n : nums) {
int count = countMap.getOrDefault(n,0)+1;
if(count >= maxCount) {
result = n;
maxCount = count;
}
// 统计次数
countMap.put(n, count);
}
// 找到超过一半的元素
return result;
}
效果:
Runtime: 9 ms, faster than 31.63% of Java online submissions for Majority Element.
Memory Usage: 44 MB, less than 44.51% of Java online submissions for Majority Element.
性能太差了。
java-次数超过一半
public int majorityElement(int[] nums) {
int limit = nums.length >> 1;
Map<Integer, Integer> countMap = new HashMap<>(limit);
for(int n : nums) {
int count = countMap.getOrDefault(n,0)+1;
if(count > limit) {
return n;
}
countMap.put(n, count);
}
// 异常
return -1;
}
效果:
Runtime: 7 ms, faster than 41.56% of Java online submissions for Majority Element.
Memory Usage: 44.4 MB, less than 29.86% of Java online submissions for Majority Element.
这两种方法,理论上都是 O(N),但是性能确实是太差了。
那有没有其他解法呢?
思路1-排序
如果一个元素超过一半,那么原始的数组排序之后,中间的元素肯定满足条件。
java 实现
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length >> 1];
}
效果:
Runtime: 1 ms, faster than 99.95% of Java online submissions for Majority Element.
Memory Usage: 42 MB, less than 92.66% of Java online submissions for Majority Element.
复杂度
当然,这个时间复杂度取决于排序算法的复杂度。
一般是 O(nlogn)
思路2-摩尔投票法
假设数组中每个不同的数字就代表一个国家,而数字的个数就代表这个国家的人数,他们在一起混战,就是每两个两个同归于尽。
我们就可以知道那个人数大于数组长度一半的肯定会获胜。
就算退一万步来说,其他的所有人都来攻击这个人数最多的国家,他们每两个两个同归于尽,最终剩下的也是那个众数。
java 实现
public int majorityElement(int[] nums) {
int major = nums[0];
int count = 1;
for(int i = 1; i < nums.length; i++) {
int num = nums[i];
// 全部抵消,重新赋值
if(count == 0) {
count++;
major = num;
} else if(major == num) {
// 自己阵营,+1
count++;
} else {
// 敌方阵营,-1
count--;
}
}
return major;
}
复杂度
效果:
Runtime: 1 ms, faster than 99.95% of Java online submissions for Majority Element.
Memory Usage: 42.4 MB, less than 59.78% of Java online submissions for Majority Element.
时间复杂度:O(N)
空间复杂度:O(1)
这个解法,就是最满足进阶的一个解法。
多数元素 II
題目
给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1)的算法解决此问题。
示例 1:
输入:[3,2,3]
输出:[3]
示例 2:
输入:nums = [1]
输出:[1]
示例 3:
输入:[1,1,1,3,3,2,2,2]
输出:[1,2]
提示:
1 <= nums.length <= 5 * 10^4
-10^9 <= nums[i] <= 10^9
思路0-HashMap 记数
和上面一题类似,我们直接借助 HashMap 实现次数的统计。
java 实现
public List<Integer> majorityElement(int[] nums) {
// 最多两个元素
List<Integer> results = new ArrayList<>(2);
int limit = nums.length / 3;
Map<Integer, Integer> map = new HashMap<>();
for(int num : nums) {
int count = map.getOrDefault(num, 0) + 1;
if(count > limit && !results.contains(num)) {
results.add(num);
}
map.put(num, count);
}
return results;
}
效果:
Runtime: 8 ms, faster than 29.02% of Java online submissions for Majority Element II.
Memory Usage: 42.3 MB, less than 78.51% of Java online submissions for Majority Element II.
虽然问题解决了,但是性能实在是太差了。
优化思路1-摩尔投票法
到底应该怎么优化呢?
我们上一题中的排序和摩尔投票法,好像都无法直接使用了。
实际上,摩尔投票法是可以改良的。
我们可以根据摩尔投票法,找到前两个最多的元素。
核心流程如下:
(1)利用摩尔投票法找到投票最多的 2 個元素
(2)统计两个元素出现的次数,超过 1/3 则加入到结果中
java 实现
int one = nums[0];
int two = nums[0];
int countOne = 0;
int countTwo = 0;
for(int n : nums) {
// 投票
if(one == n) {
countOne++;
continue;
}
if(two == n) {
countTwo++;
continue;
}
// 第1个候选人配对
if (countOne == 0) {
one = n;
countOne++;
continue;
}
// 第2个候选人配对
if (countTwo == 0) {
two = n;
countTwo++;
continue;
}
// 其他情况,二者都抵消
countOne--;
countTwo--;
}
// 计数阶段
countOne = 0;
countTwo = 0;
for(int n : nums) {
if(n == one) {
countOne++;
} else if(n == two) {
countTwo++;
}
}
int limit = nums.length / 3;
List<Integer> results = new ArrayList<>();
if(countOne > limit) {
results.add(one);
}
if(countTwo > limit) {
results.add(two);
}
// 添加元素
return results;
}
效果:
Runtime: 1 ms, faster than 99.86% of Java online submissions for Majority Element II.
Memory Usage: 43.1 MB, less than 30.73% of Java online submissions for Majority Element II.
优化思路2-排序
还记得上一题中的排序算法的优雅吗?
其实这一题还是可以使用排序解决的,不过相对麻烦很多。
这里记录下来,给大家提供一种思路:
public List<Integer> majorityElement(int[] nums) {
List<Integer> res = new ArrayList<>();
if (nums == null || nums.length == 0) return res;
if (nums.length == 1) {
res.add(nums[0]);
return res;
} else if (nums.length == 2) {
if (nums[0] == nums[1]) {
res.add(nums[0]);
return res;
}
res.add(nums[0]);
res.add(nums[1]);
return res;
}
// 核心逻辑
quickSort(nums, 0, nums.length - 1, res);
return res;
}
private void quickSort(int[] nums, int start, int end, List<Integer> res) {
int threshold = nums.length / 3;
if (end - start + 1 <= threshold) return;
int pivot = nums[end];
int pos = start - 1;
int count = 1;
for (int i = start; i < end; i++) {
if (nums[i] < pivot) {
swap(nums, ++pos, i);
} else if (nums[i] == pivot) {
swap(nums, pos + count, i);
count++;
}
}
swap(nums, pos + count, end);
if (count > threshold) {
res.add(pivot);
}
quickSort(nums, start, pos, res);
quickSort(nums, pos + count, end, res);
}
private void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
这里主要是对快速排序的利用,我们下一节还是回顾一下常用的排序算法吧。
排序这个东西,一看就会,一写就废。
小结
希望本文对你有帮助,如果有其他想法的话,也可以评论区和大家分享哦。
各位极客的点赞收藏转发,是老马持续写作的最大动力!
参考资料
https://leetcode-cn.com/problems/number-of-digit-one/