Java求众数

Java求众数

  • 前言
  • 一、例题、解答
    • 1、例题
    • 2、解答
      • A、HashMap
      • B、排序
      • C、栈
      • D、比武招亲
      • E、Random
      • F、分治
  • 总结
  • 参考文献

前言

用Java来求一个数组的众数,可使用HashMap、栈等数据结构完成。

一、例题、解答

1、例题

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2

2、解答

A、HashMap

用HashMap快速解题,记录每个元素出现的个数,如果个数大于 len / 2 ,认为其就是众数。

public int majorityElement(int[] nums) {
        //Hash Map快速解题
        //用map记录num的长度,然后判断它是否大于len/2
        Map<Integer, Integer> record = new HashMap<>();
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            int n = record.getOrDefault(nums[i], 0) + 1;
            if (n > len / 2)
                return nums[i];
            record.put(nums[i], n);
        }
        return 0;
    }

B、排序

对于数组,可对其排序,而众数一定在中间。

public int majorityElement2(int[] nums) {
        //排序,值必然在中间
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }

C、栈

如果栈为空 或者 栈顶 == num,则将 num 进栈;
否则,不将 num 入栈且栈顶元素出栈;
这样最后栈里的元素都是众数。

public int majorityElement3(int[] nums) {
        //用栈来进入所有元素,一旦碰到不同元素就出栈,由于个数大于数组的一半,所以最终剩下的元素都为返回值。
        Deque<Integer> record = new LinkedList<>();
        for (int i = 0; i < nums.length; i++) {
            if (!record.isEmpty() || record.peek() == nums[i]) {
                record.push(nums[i]);
                continue;
            }
            record.pop();
        }
        return record.peek();
    }

D、比武招亲

专业名称也叫摩尔选票,根据数组元素来记录当前值的票数,如果等于,则票数加1,否则-1。减为0时,重新记录新值。
将数组分为两组,一组为众数,一组为非众数。众数的个数 > 非众数的个数。所以最终会记录的数为众数。

public int majorityElement4(int[] nums) {
        //比武招亲
        int res = 0, count = 0;
        for (int num : nums) {
            if (count == 0) {
                res = num;
                count = 1;
                continue;
            }
            count += num == res ? 1 : -1;
        }
        return res;
    }

E、Random

将数组分为两组,一组为众数,一组为非众数。众数的个数 > 非众数的个数。所以随机选择一个数统计其次数,然后看它的次数是否 > len / 2,来大概率随机寻找众数。

//random
    public int majorityElement5(int[] nums) {
        //既然有个数它的个数这么多,我们随便挑选一个数统计它出现的次数,如果它的个数超过len/2,则返回
        int res;
        while (true) {
            Random rand = new Random();
            int index = getRandom(rand, 0, nums.length);
            if (getCount(nums, nums[index]) > nums.length / 2)
                return nums[index];
        }
    }

    public int getRandom(Random rand, int min, int max) {
        return rand.nextInt(max - min);
    }

    public int getCount(int[] nums, int n) {
        int count = 0;
        for (int num : nums) {
            count += num == n ? 1 : 0;
        }
        return count;
    }

F、分治

将数组对半砍,要么两组数组的众数一样,即为整个nums数组的众数;要么必有一半数组的众数为整个nums数组的众数,如果两边众数不一样,则统计各区间的众数个数。

//分治
    public int majorityElement6(int[] nums) {
        //将数组对半砍,要么两边的众数一样,要么必有一半有众数,如果两边众数不一样,则统计各区间的众数个数
        return getResByRecursion(nums, 0, nums.length - 1);
    }

    public int getCountByLR(int[] nums, int n, int left, int right) {
        int count = 0;
        for (int i = left; i <= right; i++) {
            count += n == nums[i] ? 1 : 0;
        }
        return count;
    }

    public int getResByRecursion(int[] nums, int left, int right) {
        if (left == right)
            return nums[left];
        //注意mid的取法mid != right/2,而是(left+right)/ 2 或者(right -left) / 2 + left;
        int mid = (left + right) / 2;
        int leftN = getResByRecursion(nums, left, mid);
        int rightN = getResByRecursion(nums, mid + 1, right);
        if (leftN != rightN) {
            return getCountByLR(nums, leftN, left, mid) > getCountByLR(nums, rightN, mid + 1, right) ? leftN : rightN;
        }
        return leftN;
    }

总结

1)HashMap,快速存取。Time:O(n),Space:O(n)
2)排序,Time:O(nlog2n),Space:O(1)
3)栈,Time:O(n),Space:O(n)
4)摩尔选票,Time:O(n),Space:O(1)
5)Random,Time:O(n2),Space:O(1),虽然事件复杂度高,但这是最坏情况,而平均情况的实际运行时间没这么高,取到众数的概率 1 / 2.
6)分治,T(n) = 2T(n/2) + 2n,Time:O(nlog2n),Space:O(log2n)

参考文献

[1] Leetcode原题
[2] 官方详解

你可能感兴趣的:(数据机构与算法,java,HashMap,分治算法,栈)