题目
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋的元素。(你可以假设数组是非空的,并且给定的数组总是存在多数元素。)
问题分析
多数元素也就是众数,若将数组中元素按顺序排列,它的中间元素就是所要寻找的多数元素,且只会有一个这样的数。
⌊ n/2 ⌋ ( n/2向下取整,与Java中int整除规则相同)= n / 2;
解决方法
方法①:排序遍历法
思路:先将数组按升序排序,用标记值num记录数组中当前元素出现的次数。当num还未达到 ⌊ n/2 ⌋时就已经遍历到其他元素,就将num设置为1表示新的元素已经出现了一次,若某一元素出现次数大于⌊ n/2 ⌋就立即输出当前元素,退出程序。
public static void findMul(int[] arr) {
int a = arr.length / 2;
int num = 0; // 用来保存当前元素出现次数
Arrays.sort(arr);
for (int i = 1; i < arr.length; i++) {
if (arr[i] == arr[i-1]) {
num ++;
}else if (arr[i] != arr[i-1]) {
num = 1;
}
if (num > a) {
System.out.print(arr[i]);
return;
}
}
}
方法②:排序取中间值法
思路:先将数组按升序排序,直接返回数组中间的那个值就是要寻找的众数。
public static int manyNum(int[] nums) {
// nlogn
Arrays.sort(nums);
return nums[nums.length / 2];
}
方法③:递归(分治法)
思路:将一个数组拆分为两部分取中间位置坐标为mid = (left + right) / 2, 左半区间为[left, mid],右半区间为[mid + 1, right].
所要寻找的多数元素一定至少存在在一个子区间。就将问题拆分为:在左半区间找左边的多数元素leftNum、在右半区间找右边的多数元素rightNum;
会有两种情况:1.leftNum == rightNum 说明这个即是左半区间的多数元素也是右半区间的多数元素,就一定是寻找的多数元素,则直接返回这个数即可。
2.leftNum != rightNum 这时还无法确定到底谁是多数元素,需要将这两个数扔回到原数组中,通过遍历查找它们分别出现的次数相比较返回次数更多的那个。
public static void main(String[] args) {
int[] arr = {7, 7, 5, 7, 5, 1 ,5, 7 ,5, 5, 7, 7 ,7, 7, 7, 7};
System.out.println(findMul3(arr, 0, arr.length - 1));
}
public static int findMul3(int[] arr, int left, int right) {
// 递归的终止条件
if (left == right) {
// 区间中只有一个元素
return arr[left];
}
int mid = (left + right) / 2;
// 寻找左半区间的多数元素
int leftNum = findMul3(arr, 0, mid);
// 寻找右半区间的多数元素
int rightNum = findMul3(arr, mid + 1, right);
if (leftNum == rightNum) {
return leftNum;
}
// 此时左右多数元素不相等,要找在原数组中出现次数相对较多的那个
int leftCount = numCount(arr, leftNum);
int rightCount = numCount(arr, rightNum);
return leftCount > rightCount ? leftNum: rightNum;
}
// 设置一个方法,计算某元素在数组中出现的次数
public static int numCount(int[] arr, int num) {
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == num) {
count++;
}
}
return count;
}
方法④:投票法
思路:采用投票的思想,支持某个元素它的score就加一,不支持某个元素它的score就减一。当它的分数为0时就换另一个元素当候选元素,使用标记值num来记录当前的候选元素。当整个数组都遍历完了,当前的候选元素即为所寻找的多数元素。投票法遵循少数服从多数的原则。
注意:num不能是整型的,整型的默认值为0,可能数组第一个元素为0,就会给这个元素多加一分造成错误。应该将num定义为Integer型变量,Integer这样的引用类型默认值都为null,不会造成上述巧合引发错误。
public static void main(String[] args) {
int[] arr = {7, 7, 5, 7, 5, 1 ,5, 7 ,5, 5, 7, 7 ,7, 7, 7, 7};
System.out.println(findMul4(arr));
}
public static int findMul4(int[] arr) {
Integer num = null;
int score = 0;
for (int i = 0; i < arr.length; i++) {
// 当score=0时换候选元素
if (score == 0) {
num = arr[i];
}
if (arr[i] == num) { // 支持
score += 1;
}else { // 不支持
score -= 1;
}
}
return num;
}
总结
上述4中方法都可以找出数组中的多数元素,但是效率不一样。
其中 方法④ 是效率最高的方法,总共只用遍历一次数组;方法③要遍历两次数组;方法②和方法①都涉及到数组的排序,遍历数组的次数都是对数级的。