题目

给定一个大小为 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中方法都可以找出数组中的多数元素,但是效率不一样。
其中 方法④ 是效率最高的方法,总共只用遍历一次数组;方法③要遍历两次数组;方法②和方法①都涉及到数组的排序,遍历数组的次数都是对数级的。