{x}
blog image

Split Array With Same Average

You are given an integer array nums.

You should move each element of nums into one of the two arrays A and B such that A and B are non-empty, and average(A) == average(B).

Return true if it is possible to achieve that and false otherwise.

Note that for an array arr, average(arr) is the sum of all the elements of arr over the length of arr.

 

Example 1:

Input: nums = [1,2,3,4,5,6,7,8]
Output: true
Explanation: We can split the array into [1,4,5,8] and [2,3,6,7], and both of them have an average of 4.5.

Example 2:

Input: nums = [3,1]
Output: false

 

Constraints:

  • 1 <= nums.length <= 30
  • 0 <= nums[i] <= 104

Solution Explanation: Split Array With Same Average

This problem asks whether we can partition an array into two non-empty subarrays with equal averages. A naive approach of checking all possible partitions is computationally expensive (exponential time). The optimal solution uses a clever combination of mathematical manipulation, bit manipulation, and a hash set to achieve a significantly faster runtime.

Mathematical Simplification:

The core idea is to simplify the problem. Instead of directly comparing averages, we can work with sums. Let:

  • nums: The input array
  • n: The length of nums
  • s: The sum of all elements in nums
  • k: The number of elements in subarray A
  • s1: The sum of elements in subarray A
  • s2: The sum of elements in subarray B

If the averages are equal, then: s1 / k = s2 / (n - k)

This simplifies to: s1 * (n - k) = s2 * k

Since s1 + s2 = s, we get: s1 * (n - k) = (s - s1) * k

Solving for s1: s1 = s * k / n

This means we need to find a subarray with a sum equal to s * k / n. However, this involves floating-point arithmetic which can lead to precision issues. To avoid this, we perform a crucial step:

We multiply every element in nums by n. This transforms the equation to: s1 * n = s * k. Now, the problem becomes finding a subarray whose sum is a multiple of s. We subtract s from each element after scaling, effectively searching for a subarray whose sum is 0.

Algorithm:

  1. Preprocessing: Calculate the sum s of nums. Multiply each element in nums by n and subtract s. This converts the problem into searching for a subarray with sum 0.

  2. Divide and Conquer: Split the modified nums into two halves (left and right).

  3. Bitmask Enumeration (for each half): Iterate through all possible subarrays in the left and right halves using bit manipulation (bitmasks). Each bit in the mask represents whether or not to include an element in the subarray. The sum of the selected elements is calculated efficiently.

  4. Hash Set: Store the sums of all subarrays in the left half in a hash set (vis).

  5. Check for 0 or Opposite: For each subarray sum in the right half:

    • If the sum is 0, we have found a solution.
    • Check if the negative of the sum is present in the hash set (vis). If it is, we have found two subarrays with equal and opposite sums after the transformation; meaning the original subarrays have the same average.

Time and Space Complexity:

  • Time Complexity: O(n * 2^(n/2)). The dominant factor is the bitmask enumeration on the two halves of the array.

  • Space Complexity: O(2^(n/2)). The hash set (vis) stores sums of subarrays from one half of the array. In the worst case, it can contain all possible sums.

Code (Python):

class Solution:
    def splitArraySameAverage(self, nums: List[int]) -> bool:
        n = len(nums)
        if n == 1:
            return False
        s = sum(nums)
        for i, v in enumerate(nums):
            nums[i] = v * n - s
        m = n >> 1
        vis = set()
        for i in range(1, 1 << m):
            t = sum(v for j, v in enumerate(nums[:m]) if i >> j & 1)
            if t == 0:
                return True
            vis.add(t)
        for i in range(1, 1 << (n - m)):
            t = sum(v for j, v in enumerate(nums[m:]) if i >> j & 1)
            if t == 0 or (i != (1 << (n - m)) - 1 and -t in vis):
                return True
        return False

The code in other languages (Java, C++, Go) follows a similar structure, employing the same algorithm and data structures. The primary differences are syntax and standard library functions used for summing, hash sets, and bit manipulation.