Before attempting this problem, you should be comfortable with:
sum(A) * len(B) == sum(B) * len(A) to avoid floating-point issuesWe need to check if we can partition the array into two non-empty groups with equal averages. The naive approach is to try all possible ways to assign each element to group A or group B. For each complete assignment, we verify if both groups are non-empty and have the same average. Two groups have equal averages when sum(A) * len(B) == sum(B) * len(A).
backtrack(i, A, B) to assign element at index i to either group A or B.i == n, check if both groups are non-empty and have equal averages.A and recurse. If that fails, remove it from A, add it to B, and recurse.true if any assignment works, false otherwise.class Solution:
def splitArraySameAverage(self, nums: List[int]) -> bool:
def backtrack(i, A, B):
if i == len(nums):
if not A or not B:
return False
return sum(A) * len(B) == sum(B) * len(A)
A.append(nums[i])
if backtrack(i + 1, A, B):
return True
B.append(nums[i])
A.pop()
res = backtrack(i + 1, A, B)
B.pop()
return res
return backtrack(0, [], [])The backtracking solution recomputes many states. We can track the state as (index, size of A, sum of A) since B is determined by what remains. If A has size s and sum curSum, then B has size n - s and sum total - curSum. We check the equal average condition at each step and memoize to avoid redundant work.
dfs(i, size, currSum) where size and currSum refer to the current subset.size > 0 and size < n, check if currSum * (n - size) == (total - currSum) * size.(i, size, currSum).class Solution:
def splitArraySameAverage(self, nums: List[int]) -> bool:
total = sum(nums)
n = len(nums)
memo = {}
def dfs(i, size, curr_sum):
if (i, size, curr_sum) in memo:
return memo[(i, size, curr_sum)]
if size > 0 and size < n and curr_sum * (n - size) == (total - curr_sum) * size:
return True
if i == n:
return False
# include nums[i] in A
if dfs(i + 1, size + 1, curr_sum + nums[i]):
memo[(i, size, curr_sum)] = True
return True
# include nums[i] in B
if dfs(i + 1, size, curr_sum):
memo[(i, size, curr_sum)] = True
return True
memo[(i, size, curr_sum)] = False
return False
return dfs(0, 0, 0)Where is the size of the input array , and is the sum of the elements of the array.
For two groups to have the same average as the whole array, we need: sum(A) / len(A) = total / n. This means sum(A) = len(A) * total / n. We only need to find a subset A of size a (where 1 <= a <= n/2) with sum exactly a * total / n. This sum must be an integer, so we only check sizes where a * total is divisible by n.
a from 1 to n/2 where a * total % n == 0:a * total / n.a has this exact sum.true if any valid subset is found.class Solution:
def splitArraySameAverage(self, nums: List[int]) -> bool:
n, total = len(nums), sum(nums)
# len(A) = a, len(B) = b, let a <= b
# avg(A) = avg(B)
# sum(A) / a = sum(B) / b = sum(nums) / n
# sum(A) / a = avg => sum(A) = a * avg
# sum(A) = a * sum(nums) / n
# Find if any subset exists with a * sum(nums) / n
# a is in the range [1, (n//2)]
memo = {}
def dfs(i, a, s):
if (i, a, s) in memo:
return memo[(i, a, s)]
if a == 0:
return s == 0
if i == n or a < 0:
return False
memo[(i, a, s)] = dfs(i + 1, a, s) or dfs(i + 1, a - 1, s - nums[i])
return memo[(i, a, s)]
for a in range(1, n // 2 + 1):
if total * a % n == 0:
if dfs(0, a, total * a // n):
return True
return FalseWhere is the size of the input array , and is the sum of the elements of the array.
We can build all achievable sums for each subset size using dynamic programming. For each element, we update what sums are achievable for each size. We iterate backwards through sizes to avoid using the same element twice. Finally, we check if any valid target sum exists for sizes 1 through n/2.
dp[a] as a set of achievable sums for subsets of size a. Initialize dp[0] = {0}.n/2 down to 1:dp[a-1], add sum + num to dp[a].a from 1 to n/2:a * total % n == 0 and the target sum a * total / n exists in dp[a], return true.false if no valid partition exists.class Solution:
def splitArraySameAverage(self, nums: List[int]) -> bool:
n = len(nums)
# len(A) = a, len(B) = b, let a <= b
# avg(A) = avg(B)
# sum(A) / a = sum(B) / b = sum(nums) / n
# sum(A) / a = avg => sum(A) = a * avg
# sum(A) = a * sum(nums) / n
# Find if any subset exists with a * sum(nums) / n
# a is in the range [1, (n//2)]
total = sum(nums)
dp = [set() for _ in range(n // 2 + 1)]
dp[0].add(0)
for num in nums:
for a in range(n // 2, 0, -1):
for prev in dp[a - 1]:
dp[a].add(prev + num)
for a in range(1, n // 2 + 1):
if (a * total % n == 0) and (a * total // n) in dp[a]:
return True
return FalseWhere is the size of the input array , and is the sum of the elements of the array.
A common mistake is checking only whether the averages match without ensuring both groups contain at least one element. If all elements go into group A and group B is empty, that is not a valid split. Always verify that both partitions have size greater than zero before comparing averages.
Comparing averages directly using floating-point division can lead to precision errors. Instead of checking sum(A) / len(A) == sum(B) / len(B), use the mathematically equivalent integer comparison sum(A) * len(B) == sum(B) * len(A) to avoid floating-point inaccuracies.
The condition a * total % n == 0 is crucial for pruning. For a valid subset of size a, the required sum a * total / n must be an integer. Skipping this check leads to unnecessary computation for impossible subset sizes and can cause the solution to time out.
When computing sums or products like a * total, the result can exceed 32-bit integer limits for large arrays with large values. Use 64-bit integers (long/long long) for intermediate calculations to prevent overflow and incorrect results.
Since finding a valid subset A automatically determines a valid subset B (the remaining elements), you only need to check subset sizes from 1 to n/2. Checking sizes beyond n/2 is redundant because if a subset of size a works, so does the complement of size n - a.