剑指Offer面试题-39 数组中出现次数超过一半的数(哈希记录、正负抵消)

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

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

限制:
1 <= 数组长度 <= 50000

题解一(哈希记录)

class Solution {
    public int majorityElement(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        int halfLength = nums.length / 2;
        for (int num : nums) {
            int count = map.getOrDefault(num, 0);
            map.put(num, count + 1);
            if (count + 1 > halfLength) {
                return num;
            }
        }
        return 0; // 这里随便写,因为输入默认合法,所以不可能执行到这一步
    }
}

时间复杂度: O ( N ) O(N) O(N)
N N N 为数组长度。需要遍历一次数组。

空间复杂度: O ( N ) O(N) O(N)
哈希表最多需要装 N N N 个数据。

题解二(正负抵消)

题目给出的特性是:数组中存在一个数,个数超过数组长度的一半

我们暂且称这个数叫做 众数

那么能不能缩小这个特性对应的区间?从最开始的[0, array.length - 1]区间内满足这个特性,慢慢缩减成到[x, array.length - 1]区间内满足这个特性,然后继续缩减,范围越来越小,最后得出结果?

这个方法的思路就是:

  • 从数组下标为0开始,选取第一个数,假设它为众数。
  • 设置一个计数器,初始值为0。然后往后遍历数组,如果遇到和刚刚选中的数相同的数,就让计数器+1,如果遇到不同的数,就让计数器-1
  • 直到计数器再次变为0。这时,无论选中的数是否是真正的众数,后面的区间依然满足题目给出的特性。
    (1) 选中的数是真正的众数,那么它跟相同数量非众数抵消了,后面的区间仍然满足特性。
    (2) 选中的数是虚假的众数,那么在这个区间内,真正众数出现的次数少于等于一半
  • 循环上面的步骤,直到走到数组尾,最后选中的数就是真正的众数。

代码实现:

class Solution {
    public int majorityElement(int[] nums) {
        int choose = 0;
        int i = 0;
        while (i < nums.length)
        {
            int count = 0;
            choose = nums[i];
            do {
                if (nums[i] == choose) {
                    count++;
                } else {
                    count--;
                }
                i++;
            } while (count != 0 && i < nums.length);
        }
        return choose;
    }
}

时间复杂度: O ( N ) O(N) O(N)
N N N 为数组长度,只需遍历一次数组。

空间复杂度: O ( 1 ) O(1) O(1)

不能用此方法求数组中出现次数最多的数。无法保证缩减区间后还满足出现次数最多的特性,所以不要搞混淆了。

你可能感兴趣的:(算法)