ACM 选手图解 LeetCode 多数元素

269 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情


大家好呀,我是帅蛋。

今天解决多数元素,又是一道分治算法的练手题。

多数元素,题目的定义是指在数组中出现次数大于 n/2 的元素。

说白了就是求数组的众数

众数是一个数学名词,常用在统计学中,是一组数据中出现次数最多的数值,代表数据的一般水平。

下面让我们来搞一下这道题。

169-1

LeetCode 169:多数元素

题意

给定一个大小为 n 的数组,找到其中的多数元素。

多数元素是指在数组中出现次数大于 n/2 的元素。

示例

输入:[2,2,1,1,1,2,2]

输出:2

提示

  • 数组非空,且给定的数组总是存在多数元素。

题目解析

本题求多数元素,难度简单。

这道题解法众多,既然准备用分治算法来解决,还是按照分治求解的三步走:

  • 划分(Divide)
  • 求解(Conquer)
  • 合并(Combine)

那整个问题的解决步骤就很明确了:

(1) 划分

划分就是拆解到问题的最小规模,这里还是用到了二分的思想。

每次将数组拆分为左右两个区间,直至拆成最小规模的问题,每个区间只有一个数。

(2) 求解

递归的求解划分之后的子问题。

在最小的区间里,每个区间只有一个数,那该区间的众数该数。

(3) 合并

一步步的向上合并,合并过程中分为两种情况:

第一种:左右两个区间的众数相同,那直接返回这个众数。

第二种:左右两个区间的众数不同,这时就将两个区间合并,在合并后的区间种计算出这两个众数出现的次数,将数值大的众数返回。

这里面要注意一点的是,可能出现左右两个众数在合并后的区间中出现的次数相同,这种情况就先随便返回一个即可

因为这只是过程中的产生情况,在合并的最终解不会出现这种情况。

毕竟【提示】中说了:数组非空,且给定的数组总是存在多数元素。

图解

以 nums = [2,2,,1,1,1,2,2] 为例。

第 1 步,划分:

169-2

# 递归划分左右区间
mid = left + (right - left) // 2
maxLeft = self.getMajority(nums, left, mid)
maxRight = self.getMajority(nums, mid + 1, right)

第 2 步,求解:

169-3

if left == right:
    return nums[left]

第 3 步,合并:

169-4

if maxLeft == maxRight:
    return maxLeft
​
# 如果左右众数不相等
else:
    leftCnt, rightCnt = 0, 0
    
    # 合并区间找众数
    for i in range(left, right + 1):
        if maxLeft == nums[i]:
            leftCnt += 1
​
        if maxRight ==  nums[i]:
            rightCnt += 1
​
    if leftCnt >= rightCnt:
        return maxLeft
    else:
        return maxRight

本题解不断将数组分为左右区间,这个时间复杂度是 O(logn),在向上合并解的时候,在左右众数不相等的时候,需要遍历合并后的区间,判断众数 leftCnt 和 rightCnt 的大小,这个的时间复杂度为 O(n),所以总的时间复杂度为 O(nlogn)

分治算法没有直接分配额外的数组空间,但是在递归过程中调用了额外的栈空间,分治算法相当于每次将当前问题拆成了两部分,n 在变为 1 之前需要进行 O(logn) 次递归,所以空间复杂度为 O(logn)

代码实现

Python 代码实现

class Solution:
​
    def getMajority(self, nums:List[int], left, right):
​
        if left == right:
            return nums[left]
​
        # 递归划分左右区间
        mid = left + (right - left) // 2
        maxLeft = self.getMajority(nums, left, mid)
        maxRight = self.getMajority(nums, mid + 1, right)
​
        # 如果左边众数 = 右边的众数
        if maxLeft == maxRight:
            return maxLeft
​
        # 如果左右众数不相等
        else:
            leftCnt, rightCnt = 0, 0
            
            # 合并区间找众数
            for i in range(left, right + 1):
                if maxLeft == nums[i]:
                    leftCnt += 1
​
                if maxRight ==  nums[i]:
                    rightCnt += 1
​
            if leftCnt >= rightCnt:
                return maxLeft
            else:
                return maxRight
​
    def majorityElement(self, nums: List[int]) -> int:
​
        return self.getMajority(nums, 0, len(nums) - 1)

Java 代码实现

class Solution {
    private int getCnt(int[] nums, int num, int left, int right) {
        int cnt = 0;
        for (int i = left; i <= right; i++) {
            if (nums[i] == num) {
                cnt++;
            }
        }
        return cnt;
    }
​
    private int getMajority(int[] nums, int left, int right) {
        if (left == right) {
            return nums[left];
        }
​
        int mid = left + (right - left) / 2;
        int maxLeft = getMajority(nums, left, mid);
        int maxRight = getMajority(nums, mid + 1, right);
​
        if (maxLeft == maxRight) {
            return maxLeft;
        }
​
        int leftCnt = getCnt(nums, maxLeft, left, right);
        int rightCnt = getCnt(nums, maxRight, left, right);
​
        return leftCnt >= rightCnt ? maxLeft : maxRight;
    }
​
    public int majorityElement(int[] nums) {
        return getMajority(nums, 0, nums.length - 1);
    }
}

图解多数元素到这就结束辣,大噶伙儿学废了嘛?

169-5

还是那句话,分治算法的学习还是要多思考多练习,慢慢来了感觉,就会觉得很简单

大家加油,记得动手帮我点赞 呀!

我是帅蛋,我们下次见~