Leetcode-169. Majority Element

这篇博客详细介绍了多种寻找数组中众数(出现次数超过n/2的元素)的方法,包括哈希表计数、排序、随机化、分治以及Boyer-Moore投票算法。每种方法都分析了其时间复杂度和空间复杂度,例如哈希表和投票算法可以在线性时间内完成,而排序则需要O(nlogn)的时间。博客提供了不同场景下选择合适算法的思路,并给出了具体的Java实现代码。
摘要由CSDN通过智能技术生成

题目

linear 线性的

  1. Majority Element

Given an array nums of size n, return the majority element.

The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.

Example 1:

Input: nums = [3,2,3]
Output: 3

Example 2:

Input: nums = [2,2,1,1,1,2,2]
Output: 2

Constraints:

  • n == nums.length
  • 1 <= n <= 5 * 104
  • -109 <= nums[i] <= 109

Follow-up: Could you solve the problem in linear time and in O(1) space?

解法

方法一:Map计数方法(自己写的)

class Solution {
    public int majorityElement(int[] nums) {
        Map<Integer, Integer> countret = new HashMap<>();

        int n = nums.length;

        for (int i = 0; i < n; i++) {
            int e = nums[i];
            if (countret.containsKey(e)) {
                int c = countret.get(e);
                countret.put(e, c + 1);
            } else {
                countret.put(e, 1);
            }
        }

        Set<Map.Entry<Integer, Integer>> entrySet = countret.entrySet();

        for (Map.Entry<Integer, Integer> retEntryset : entrySet) {
            Integer e = retEntryset.getKey();
            Integer ec = retEntryset.getValue();

            if (ec > n / 2) {
                return e;
            }
        }

        return -1;
    }
}

方法二:哈希表

思路
我们知道出现次数最多的元素大于【n/2】次,所以可以用哈希表来快速统计每个元素出现的次数。

算法:
我们使用哈希映射(HashMap)来存储每个元素以及出现的次数。对于哈希映射中的每个键值对,键表示一个元素,值表示该元素出现的次数。

我们用一个循环遍历数组nums并将数组中的每个元素加入哈希映射中。在这之后,我们遍历哈希映射中所有键值对,返回值最大的键。我们同样也可以在遍历数组nums的时候使用打擂台的方法,维护最大的值,这样省去了最后对哈希映射的遍历。

代码

    /**
     * Given an array nums of size n, return the majority element.
     * <p>
     * The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.
     * <p>
     * 来源:力扣(LeetCode)
     * 链接:https://leetcode.cn/problems/majority-element
     * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
     *
     * @author eistert
     * @date 15:50 2022/6/4
     **/
    class Solution {
        public int majorityElement(int[] nums) {
            Map<Integer, Integer> countRetMap = countmap(nums);

            Map.Entry<Integer, Integer> majorityEntry = null;

            for (Map.Entry<Integer, Integer> entry : countRetMap.entrySet()) {
                if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                    majorityEntry = entry;
                }
            }

            return majorityEntry.getKey();
        }


        public Map<Integer, Integer> countmap(int[] nums) {
            Map<Integer, Integer> countRetMap = new HashMap<>();
            int n = nums.length;
            for (int i = 0; i < n; i++) {
                if (countRetMap.containsKey(nums[i])) {
                    countRetMap.put(nums[i], countRetMap.get(nums[i]) + 1);
                } else {
                    countRetMap.put(nums[i], 1);
                }
            }

            return countRetMap;
        }
    }

复杂度分析

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

方法三:排序

思路:
如果将数组nums中所有元素按照单调递增或者单调递减的顺序排序,那么下标为【n/2】的元素(下标从0开始)一定是众数。

算法:
对于这种算法,我们先将nums数组排序,然后返回上文所说的下标对应的元素。
在这里插入图片描述
对于每种情况,数组下面的线表示如果众数是数组中的最小值时覆盖的下标,数组上面的线表示如果众数是数组中的最大值时覆盖的下标。对于其他的情况,这条线会在这两种极端的情况的中间。对于这两种极端情况,它们会在下标为【n/2】的地方有重叠。因此,无论众数是多少,返回【n/2】下标对应的值都是正确的。

代码

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length / 2];
    }
}

复杂度分析

  • 时间复杂度:O(nlogn)。
  • 空间复杂度:O(logn)。

方法三:随机化

思路
因为超过[n/2]的数组下标被众数占据了,这样我们随机挑选一个下标对应的元素并验证,有很大的概率能找到众数。

算法
由于一个给定的下标对应的数字很有可能是众数,我们随机挑选一个下标,检查它是否是众数,如果是就返回,否则继续随机挑选。

    class Solution2 {
        // 数组范围
        private int numsRange(Random rand, int min, int max) {
            return rand.nextInt((max - min) + min);
        }


        private int countOccurences(int[] nums, int num) {
            int count = 0;
            for (int i = 0; i < nums.length; i++) {
                if (nums[i] == num) {
                    count++;
                }
            }
            return count;
        }

        public int majorityElement(int[] nums) {

            Random rand = new Random();

            int majorityCount = nums.length / 2;

            while (true) {
                int condidate = nums[numsRange(rand, 0, nums.length)];

                if (countOccurences(nums, condidate) > majorityCount) {
                    return condidate;
                }
            }

        }
    }

复杂度分析:期望的时间复杂度为 O(n)。
空间复杂度:O(1)。随机方法只需要常数级别的额外空间。

方法四:分治

思路
如果数a是数组nums的众数,如果我们将nums分成两部分,那么a必定是至少一部分的众数。
我们可以使用反证法来证明这个结论。假设a既不是左半部分的众数,也不是右半部分的众数,那么a出现的次数少于(l/2 + r / 2)次,其中l和r分别是左半部分和右半部分的长度。由于(l/2 + r / 2)<= (l + r) / 2,说明a也不是数组的众数,因此出现了矛盾。所以这个结论是正确的。

这样以来,我们就可以使用分治法解决这个问题:将数组分成两部分,分别求出左半部分的众数a1以及右半部分的众数a2,随后在a1和a2中选出正确的众数。

算法

我们使用经典的分治算法递归求解,直到所有的子问题都是长度为1的数组。长度为1的子数组中 唯一的数显然是众数,直接返回即可。如果回溯后某区间的长度大于1,我们必须将左右子区间的值合并。如果他们的众数相同,那么显然这一段的众数是他们相同的值。否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。

代码

    /**
     * 方法四:分治
     *
     * @author aiwenwen
     * @date 19:06 2022/6/4
     **/
    class Solution4 {
        private int countInRange(int[] nums, int num, int low, int hig) {
            int count = 0;
            for (int i = low; i <= hig; i++) {
                if (nums[i] == num) {
                    count++;
                }
            }
            return count;
        }

        private int majorityElementRec(int[] nums, int low, int hig) {
            // base case; the only element in an array of size 1 is the majority element
            if (low == hig) {
                return nums[low];
            }

            // recurse on left and right halves of this slice
            int mid = (hig - low) / 2 + low;
            int left = majorityElementRec(nums, low, mid);
            int right = majorityElementRec(nums, mid + 1, hig);


            // if the two halves agree on the majority element,return it.
            if (left == right) {
                return left;
            }

            // otherwise,count each element and return the "winner".
            int leftcount = countInRange(nums, left, low, hig);
            int rightcount = countInRange(nums, right, low, hig);


            return leftcount > rightcount ? left : right;
        }


        public int majorityElement(int[] nums) {

            return majorityElementRec(nums, 0, nums.length - 1);
        }
    }

方法五:Boyer-Moore 投票算法


    /**
     * 方法五:Boyer-Moore 投票算法
     *
     * @author aiwenwen
     * @date 18:04 2022/6/4
     **/
    class Solution3 {
        public int majorityElement(int[] nums) {
            int count = 0;
            Integer candidate = null;

            for (int num : nums) {
                if (count == 0) {
                    candidate = num;
                }

                count += (num == candidate) ? 1 : -1;

            }

            return candidate;

        }
    }

转载

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/majority-element/solution/duo-shu-yuan-su-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值