算法练习---力扣805: 数组的均值分割

本文主要是对官方题解的个人理解, 旨在学习

如果对你有用的话, 麻烦点个赞, 感激不尽

目录

一. 问题描述

二. 问题分析

三. 关键点

四. 代码实现及解释


一. 问题描述 

1. 力扣链接: 力扣805:数组的均值分割

2. 问题描述: 给定数组nums, 长度1到30, 里面元素是0到10000的整数, 要求把数组分成两组A和B, 并且这两组的平均数相等

二. 问题分析

1. 分成两组, 每一个数据可能在A组, 也可能在B组, 每个数字有两种分发, 那30个数字就有2^{30}种分法, 粗暴的做法就是每种可能都遍历一遍, 但是会执行超时. 通过将数组分为前后两段, 分别遍历, 那就变成了2^{n/2}

2. 根据题意, 分组后的平均值相同, 这样的话, 平均值其实就是整个nums的平均值, 因此可以把每一个元素都减去平均值, 就会得到一个均值是零的数据组, 这时, 只要找到某个元素的集合的总和是0, 那就是可以完成分组(当然不能能把所有元素都取完, 要给B组留个种子才行). 

减去平均值的做法比较巧妙了, 但是官方解答也提示了, 可能引入浮点数的问题, 可通过先将数组中数据都乘以数组长度来解决.

3. 将减掉均值的新数组分成两段, 遍历前半段, 并记录每一种取法的总和A1; 然后在遍历后半段时, 就可以将后半段种每一的选取结果进行求和得到B1, 如果-B1在前半段计算的结果集合里, 那就是判断可以完成分组.

三. 关键点

 如何实现分组: 即2^{n/2}种选取方案怎么通过代码实现, 可以通过二进制枚举的方式实现.

比如前半段数组长度是2, 那就是4种可能的选取方式, 那就遍历数字1到3(range(1,)4).

遍历1到3, 只有三种选取结果呀, 不对呀, 被忘了其中一种吧? 其实有一种是所有的元素都不选, 是不合格的, 因为一个都不选, 那就是元素和是0, 这种情况会影响集合有数据时, 和为0的情况, 所以不遍历数字0, 从1开始

数字1时:

1). 将数字1进行位运算, 右移0位, 然后和1做与运算 得到结果1, 表示选中第一个元素

2). 将数字1进行位运算, 右移1位, 然后和1做与运算 得到结果0, 表示未选中第二个元素

此时一种选取方式就完成了

数字2时:

1). 将数字2进行位运算, 右移0位, 然后和1做与运算 得到结果0, 表示未选中第一个元素

2). 将数字2进行位运算, 右移1位, 然后和1做与运算 得到结果1, 表示选中第二个元素

此时另一种选取方式就完成了

数字3时:

1). 将数字3进行位运算, 右移0位, 然后和1做与运算 得到结果1, 表示选中第一个元素

2). 将数字3进行位运算, 右移1位, 然后和1做与运算 得到结果1, 表示选中第二个元素

此时另一种选取方式就完成了

四. 代码实现及解释

class Solution:
    def splitArraySameAverage(self, nums: List[int]) -> bool:
        n = len(nums)
        if n == 1:
            return False

        s = sum(nums)
        for i in range(n):  # 处理数据, 元素统一乘以n, 减去平均数. 因为元素都扩大了n倍, 所以平均值也扩大了n倍, 减去s就是减去平均值的n倍
            nums[i] = nums[i] * n - s

        m = n // 2  # 一半 取整, 避免n是奇数时, m不是整数的问题
        left = set()
        for i in range(1, 1 << m):  # 1做位运算, 向右移动m位, 其实就是变成了2的m次方, 就是取法的总数
            tot = sum(x for j, x in enumerate(nums[:m]) if i >> j & 1)
            # 上面的方法就是三中提到的核心点的写法, 数字向左移动0位, 1位等等的操作, 每一种取数方法的演绎
            if tot == 0:
                # 有零存在, 就说明可以实现分组了, 可提前结束
                return True
            left.add(tot)

        rsum = sum(nums[m:])
        for i in range(1, 1 << (n - m)):  # 后半段的遍历
            tot = sum(x for j, x in enumerate(nums[m:]) if i >> j & 1)
            if tot == 0 or rsum != tot and -tot in left:
                # 当rsum == tot时, 说明是后半段的所有数都取到了, 那-tot in left就一定会满足, 此时是不符合要求的, 因为前半段中有一种情况是前半段全部都取了, 后半段如果也全部都取, 那就变成只分一组了, 因此要求rsum != tot
                return True
        return False

作者:力扣官方题解
链接:https://leetcode.cn/problems/split-array-with-same-average/solutions/1966163/shu-zu-de-jun-zhi-fen-ge-by-leetcode-sol-f630/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(算法练习,算法,leetcode,均值算法)