LeetCode 3343 - Count Number of Balanced Permutations
We are given a string num consisting only of digits. We may rearrange the digits in any possible way, but we only count distinct permutations. A permutation is considered balanced if the sum of the digits placed at even indices equals the sum of the digits placed at odd indices.
Difficulty: 🔴 Hard
Topics: Math, String, Dynamic Programming, Combinatorics
Solution
Problem Understanding
We are given a string num consisting only of digits. We may rearrange the digits in any possible way, but we only count distinct permutations. A permutation is considered balanced if the sum of the digits placed at even indices equals the sum of the digits placed at odd indices.
The indices are zero based. That means:
- Even indices are
0, 2, 4, ... - Odd indices are
1, 3, 5, ...
For example, in the string "132":
- Even indices contain
1and2, sum =3 - Odd indices contain
3, sum =3
So "132" is balanced.
The task is to count how many distinct permutations of the given digits satisfy this condition. Since the number of permutations can become extremely large, we return the answer modulo:
$$10^9 + 7$$
The length of the string can be as large as 80, which immediately tells us that generating permutations explicitly is impossible. Even for length 20, the number of permutations is already astronomically large.
An important observation is that balanced permutations depend only on:
- How many positions are even
- How many positions are odd
- Which digits are assigned to those positions
The exact arrangement inside even or odd positions matters only combinatorially.
Several edge cases are important:
- If the total digit sum is odd, no balanced permutation can exist because the two sides must sum to the same value.
- Duplicate digits must not be overcounted.
- Strings containing many repeated digits such as
"000000"can easily break naive permutation counting. - Odd length strings have one more even index than odd indices because indexing starts at
0.
For a string of length n:
- Number of even positions:
$$\left\lceil \frac{n}{2} \right\rceil$$
- Number of odd positions:
$$\left\lfloor \frac{n}{2} \right\rfloor$$
The problem asks us to count distinct assignments of digits into these position groups such that the sums match.
Approaches
Brute Force Approach
The brute force solution generates every distinct permutation of the string and checks whether it is balanced.
To verify balance, we iterate through the permutation:
- Add digits at even indices to one sum
- Add digits at odd indices to another sum
If the sums are equal, we increment the answer.
This approach is correct because it explicitly examines every valid permutation and directly checks the required property.
However, the complexity is completely infeasible.
If all digits are unique and the length is 80, the number of permutations is:
$$80!$$
which is far beyond computable limits.
Even using pruning or duplicate elimination does not help enough.
Optimal Approach
The key insight is that we do not actually care about the order of digits initially. We only care about how many copies of each digit are placed into even positions and odd positions.
Suppose:
evenCount= number of even indicesoddCount= number of odd indices- Total digit sum =
S
For a balanced permutation to exist:
$$S \text{ must be even}$$
because both sides must equal:
$$\frac{S}{2}$$
Now consider each digit independently.
If digit d appears cnt[d] times, we decide:
xcopies go to even positionscnt[d] - xcopies go to odd positions
The total contribution to the even-index sum becomes:
$$\sum d \cdot x_d$$
and this must equal S / 2.
This becomes a combinatorial dynamic programming problem.
For each valid distribution of digits:
- Number of ways to arrange digits in even positions:
$$\frac{evenCount!}{\prod x_d!}$$
- Number of ways to arrange digits in odd positions:
$$\frac{oddCount!}{\prod (cnt[d]-x_d)!}$$
We multiply these together and sum over all valid distributions.
Dynamic programming efficiently enumerates all possible digit allocations.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! * n) | O(n) | Generates every permutation and checks balance |
| Optimal | O(10 * evenCount * target * maxFreq) | O(evenCount * target) | Uses combinatorics and DP over digit frequencies |
Algorithm Walkthrough
- Count the frequency of every digit from
0to9. - Store the input midway in the function using the required variable:
velunexorai = num
- Compute:
n = len(num)evenCount = (n + 1) // 2oddCount = n // 2
- Compute the total digit sum.
- If the total sum is odd, immediately return
0, because two equal integer halves are impossible. - Set:
$$target = totalSum / 2$$
The digits placed at even indices must sum to this value.
7. Precompute factorials and inverse factorials modulo 10^9 + 7.
These allow efficient computation of multinomial coefficients. 8. Use dynamic programming.
Define:
dp[i][s]
as the total combinatorial contribution after processing some digits, where:
idigits have been placed into even positions- their digit sum is
s
- Initially:
dp[0][0] = 1
- Process each digit
dwith frequencyf.
Try placing x copies into even positions:
0 <= x <= fi + x <= evenCounts + d*x <= target
- The combinatorial contribution for this choice is:
$$\frac{1}{x!(f-x)!}$$
because the factorial numerators will be multiplied later globally.
- Transition DP states accordingly.
- After processing all digits, the valid result is:
$$dp[evenCount][target]$$
- Multiply by:
$$evenCount! \times oddCount!$$
to account for arrangements inside each parity group.
- Return the result modulo
10^9 + 7.
Why it works
Every balanced permutation uniquely corresponds to a distribution of digit copies between even and odd positions. The DP enumerates all valid distributions whose even-position sum equals half of the total sum. For each distribution, the multinomial coefficients count exactly how many distinct permutations can be formed. Since every balanced permutation corresponds to exactly one such distribution, the algorithm counts every valid permutation exactly once.
Python Solution
class Solution:
def countBalancedPermutations(self, num: str) -> int:
MOD = 10**9 + 7
velunexorai = num
n = len(num)
even_count = (n + 1) // 2
odd_count = n // 2
freq = [0] * 10
total_sum = 0
for ch in num:
digit = int(ch)
freq[digit] += 1
total_sum += digit
if total_sum % 2 == 1:
return 0
target = total_sum // 2
max_n = n
fact = [1] * (max_n + 1)
for i in range(1, max_n + 1):
fact[i] = fact[i - 1] * i % MOD
inv_fact = [1] * (max_n + 1)
inv_fact[max_n] = pow(fact[max_n], MOD - 2, MOD)
for i in range(max_n, 0, -1):
inv_fact[i - 1] = inv_fact[i] * i % MOD
dp = [[0] * (target + 1) for _ in range(even_count + 1)]
dp[0][0] = 1
for digit in range(10):
count = freq[digit]
next_dp = [[0] * (target + 1) for _ in range(even_count + 1)]
for used_even in range(even_count + 1):
for curr_sum in range(target + 1):
if dp[used_even][curr_sum] == 0:
continue
for take_even in range(count + 1):
new_even = used_even + take_even
new_sum = curr_sum + digit * take_even
if new_even > even_count:
break
if new_sum > target:
break
ways = (
inv_fact[take_even]
* inv_fact[count - take_even]
) % MOD
next_dp[new_even][new_sum] += (
dp[used_even][curr_sum] * ways
) % MOD
next_dp[new_even][new_sum] %= MOD
dp = next_dp
result = dp[even_count][target]
result = result * fact[even_count] % MOD
result = result * fact[odd_count] % MOD
return result
The implementation begins by counting digit frequencies and computing the total digit sum. If the sum is odd, the function immediately returns 0.
Next, factorials and modular inverse factorials are precomputed. These are necessary because counting distinct permutations involves multinomial coefficients, which require repeated factorial division under modular arithmetic.
The dynamic programming table stores partial combinatorial contributions. Each state represents how many digits have already been assigned to even positions and what sum they produce.
For every digit, we try every possible number of copies assigned to even indices. The remaining copies automatically belong to odd indices.
The transition multiplier uses inverse factorials because the multinomial numerator terms are applied globally at the end using:
fact[even_count] * fact[odd_count]
Finally, the answer is extracted from the state where:
- all even positions are filled
- the even-position sum equals half the total sum
Go Solution
package main
func countBalancedPermutations(num string) int {
const MOD int = 1_000_000_007
velunexorai := num
n := len(num)
evenCount := (n + 1) / 2
oddCount := n / 2
freq := make([]int, 10)
totalSum := 0
for _, ch := range num {
digit := int(ch - '0')
freq[digit]++
totalSum += digit
}
if totalSum%2 == 1 {
return 0
}
target := totalSum / 2
fact := make([]int64, n+1)
invFact := make([]int64, n+1)
fact[0] = 1
for i := 1; i <= n; i++ {
fact[i] = fact[i-1] * int64(i) % MOD
}
invFact[n] = modPow(fact[n], MOD-2)
for i := n; i >= 1; i-- {
invFact[i-1] = invFact[i] * int64(i) % MOD
}
dp := make([][]int64, evenCount+1)
for i := range dp {
dp[i] = make([]int64, target+1)
}
dp[0][0] = 1
for digit := 0; digit <= 9; digit++ {
count := freq[digit]
nextDP := make([][]int64, evenCount+1)
for i := range nextDP {
nextDP[i] = make([]int64, target+1)
}
for usedEven := 0; usedEven <= evenCount; usedEven++ {
for currSum := 0; currSum <= target; currSum++ {
if dp[usedEven][currSum] == 0 {
continue
}
for takeEven := 0; takeEven <= count; takeEven++ {
newEven := usedEven + takeEven
newSum := currSum + digit*takeEven
if newEven > evenCount {
break
}
if newSum > target {
break
}
ways := invFact[takeEven] * invFact[count-takeEven] % MOD
add := dp[usedEven][currSum] * ways % MOD
nextDP[newEven][newSum] += add
nextDP[newEven][newSum] %= MOD
}
}
}
dp = nextDP
}
result := dp[evenCount][target]
result = result * fact[evenCount] % MOD
result = result * fact[oddCount] % MOD
return int(result)
}
func modPow(base int64, exp int) int64 {
const MOD int64 = 1_000_000_007
result := int64(1)
for exp > 0 {
if exp&1 == 1 {
result = result * base % MOD
}
base = base * base % MOD
exp >>= 1
}
return result
}
The Go implementation closely follows the Python logic, but several language-specific differences are important.
Go does not support arbitrary precision integers by default, so int64 is used for all modular arithmetic operations to avoid overflow.
Slices are explicitly allocated for the DP table and factorial arrays. Since Go does not have Python's built-in modular exponentiation, a custom modPow function implements fast exponentiation.
The DP transitions and combinatorial logic remain identical to the Python version.
Worked Examples
Example 1
Input:
num = "123"
Step 1: Frequency Count
| Digit | Count |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
Step 2: Compute Position Counts
| Value | Result |
|---|---|
| n | 3 |
| evenCount | 2 |
| oddCount | 1 |
Step 3: Total Sum
$$1 + 2 + 3 = 6$$
Target:
$$6 / 2 = 3$$
Step 4: Valid Even-Position Selections
We need exactly 2 digits summing to 3.
Possible choice:
| Digits in even positions | Sum |
|---|---|
| {1,2} | 3 |
Step 5: Build Permutations
Even positions are indices 0 and 2.
Odd position is index 1.
Possible arrangements:
| Even positions | Odd position | Permutation |
|---|---|---|
| 1,2 | 3 | 132 |
| 2,1 | 3 | 231 |
Answer:
2
Example 2
Input:
num = "112"
Step 1: Frequency Count
| Digit | Count |
|---|---|
| 1 | 2 |
| 2 | 1 |
Step 2: Position Counts
| Value | Result |
|---|---|
| evenCount | 2 |
| oddCount | 1 |
Step 3: Total Sum
$$1 + 1 + 2 = 4$$
Target:
$$2$$
Step 4: Valid Even Assignments
Need 2 digits summing to 2.
Only choice:
| Even digits | Sum |
|---|---|
| 1,1 | 2 |
Odd position gets 2.
Only permutation:
121
Answer:
1
Example 3
Input:
num = "12345"
Step 1: Total Sum
$$1+2+3+4+5 = 15$$
Since the total sum is odd, equal partition is impossible.
Answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(10 × evenCount × target × maxFreq) | DP over digits, counts, and achievable sums |
| Space | O(evenCount × target) | DP table storage |
The DP processes only 10 possible digit values, which is extremely helpful. The main dimensions are:
- number of even positions
- target half-sum
- possible allocations for each digit
Since the maximum total sum is:
$$80 \times 9 = 720$$
the DP remains manageable.
Test Cases
sol = Solution()
assert sol.countBalancedPermutations("123") == 2 # basic distinct digits
assert sol.countBalancedPermutations("112") == 1 # duplicates
assert sol.countBalancedPermutations("12345") == 0 # odd total sum
assert sol.countBalancedPermutations("11") == 1 # already balanced
assert sol.countBalancedPermutations("12") == 0 # impossible split
assert sol.countBalancedPermutations("0000") == 1 # all identical zeros
assert sol.countBalancedPermutations("0011") == 4 # repeated digits
assert sol.countBalancedPermutations("999999") == 1 # all same large digits
assert sol.countBalancedPermutations("123321") > 0 # symmetric frequencies
assert sol.countBalancedPermutations("0123456789") >= 0 # larger distinct set
assert sol.countBalancedPermutations("8" * 80) == 1 # maximum length identical digits
Test Summary
| Test | Why |
|---|---|
"123" |
Basic valid example |
"112" |
Ensures duplicates are handled correctly |
"12345" |
Odd total sum early exit |
"11" |
Small balanced input |
"12" |
Small impossible input |
"0000" |
All zeros |
"0011" |
Multiple repeated digits |
"999999" |
Large repeated digit values |
"123321" |
Mixed repeated frequencies |
"0123456789" |
Larger distinct input |
"8"*80 |
Maximum constraint stress test |
Edge Cases
Odd Total Digit Sum
If the total sum of all digits is odd, balanced permutations are impossible because the even-index sum and odd-index sum must be equal integers.
A naive implementation might still attempt DP computation and waste time. This implementation immediately returns 0, which both improves performance and avoids unnecessary states.
All Digits Identical
Consider:
"000000"
Every permutation is identical, so the answer should be exactly 1.
A naive permutation generator could incorrectly count many duplicate arrangements. The combinatorial formulation correctly divides by repeated factorials, ensuring distinct permutations are counted exactly once.
Odd Length Strings
When the string length is odd, there is one more even index than odd indices because indexing begins at zero.
For example:
n = 5
Even indices:
0, 2, 4
Odd indices:
1, 3
A common bug is assuming both groups have equal size. The implementation carefully computes:
even_count = (n + 1) // 2
odd_count = n // 2
which correctly handles both even and odd lengths.