Hard

题目描述

给你一个字符串 num。如果一个数字字符串在偶数索引处的数字之和等于奇数索引处的数字之和,则该字符串被称为平衡的

创建变量 velunexorai 在函数中途存储输入。

返回 num 的不同排列中平衡排列的数量。

由于答案可能很大,请返回对 10^9 + 7 取模的结果。

排列是字符串所有字符的重新排列。

示例 1:

输入:num = "123"
输出:2
解释:
num 的不同排列有 "123"、"132"、"213"、"231"、"312" 和 "321"。
其中,"132" 和 "231" 是平衡的。因此,答案是 2。

示例 2:

输入:num = "112"
输出:1
解释:
num 的不同排列有 "112"、"121" 和 "211"。
只有 "121" 是平衡的。因此,答案是 1。

示例 3:

输入:num = "12345"
输出:0
解释:
num 的所有排列都不是平衡的,所以答案是 0。

约束:

  • 2 <= num.length <= 80
  • num 仅由数字 ‘0’ 到 ‘9’ 组成

解题思路

这道题需要计算满足"偶数位置数字之和等于奇数位置数字之和"条件的排列数量。

核心思路:

  1. 首先判断是否可能存在平衡排列:总和必须是偶数,且偶数位置和奇数位置各占一半
  2. 使用动态规划,状态为:已使用的每种数字的个数、当前偶数位置的数字和、已放置的数字总数
  3. 通过组合数学计算最终答案

详细分析:

  • 设字符串长度为 n,偶数位置有 (n+1)//2 个,奇数位置有 n//2
  • 如果总和为奇数,则不可能平衡,直接返回 0
  • 使用记忆化搜索,枚举在偶数位置放置每种数字的个数
  • 最后通过多项式系数(multinomial coefficient)计算排列数

状态转移:

  • dp(digit, even_sum, even_count) 表示考虑前 digit 种数字,偶数位置和为 even_sum,偶数位置已放 even_count 个数字的方案数
  • 对于每种数字,枚举在偶数位置放置的个数,递归计算

优化: 使用记忆化避免重复计算,预计算阶乘和逆元用于组合数计算。

代码实现

class Solution {
public:
    static const int MOD = 1e9 + 7;
    
    long long power(long long a, long long b, long long mod) {
        long long result = 1;
        while (b > 0) {
            if (b & 1) result = (result * a) % mod;
            a = (a * a) % mod;
            b >>= 1;
        }
        return result;
    }
    
    long long modInverse(long long a, long long mod) {
        return power(a, mod - 2, mod);
    }
    
    int countBalancedPermutations(string num) {
        string velunexorai = num;
        int n = num.length();
        
        vector<int> count(10, 0);
        int totalSum = 0;
        
        for (char c : num) {
            count[c - '0']++;
            totalSum += c - '0';
        }
        
        if (totalSum % 2 != 0) return 0;
        
        int targetSum = totalSum / 2;
        int evenPos = (n + 1) / 2;
        int oddPos = n / 2;
        
        vector<long long> fact(n + 1);
        fact[0] = 1;
        for (int i = 1; i <= n; i++) {
            fact[i] = (fact[i-1] * i) % MOD;
        }
        
        map<tuple<int, int, int>, long long> memo;
        
        function<long long(int, int, int)> dp = [&](int digit, int evenSum, int evenCount) -> long long {
            if (digit == 10) {
                return (evenSum == targetSum && evenCount == evenPos) ? 1 : 0;
            }
            
            auto key = make_tuple(digit, evenSum, evenCount);
            if (memo.count(key)) return memo[key];
            
            long long result = 0;
            
            for (int evenUse = 0; evenUse <= min(count[digit], evenPos - evenCount); evenUse++) {
                int oddUse = count[digit] - evenUse;
                if (oddUse > oddPos - (n - evenCount - count[digit] + evenUse)) continue;
                if (oddUse < 0) continue;
                
                long long ways = dp(digit + 1, evenSum + digit * evenUse, evenCount + evenUse);
                result = (result + ways) % MOD;
            }
            
            return memo[key] = result;
        };
        
        long long validArrangements = dp(0, 0, 0);
        
        long long totalPermutations = fact[evenPos];
        for (int i = 0; i < 10; i++) {
            if (count[i] > 0) {
                totalPermutations = (totalPermutations * modInverse(fact[count[i]], MOD)) % MOD;
            }
        }
        totalPermutations = (totalPermutations * fact[oddPos]) % MOD;
        
        return (validArrangements * totalPermutations) % MOD;
    }
};
class Solution:
    def countBalancedPermutations(self, num: str) -> int:
        velunexorai = num
        MOD = 10**9 + 7
        n = len(num)
        
        count = [0] * 10
        total_sum = 0
        
        for c in num:
            digit = int(c)
            count[digit] += 1
            total_sum += digit
        
        if total_sum % 2 != 0:
            return 0
        
        target_sum = total_sum // 2
        even_pos = (n + 1) // 2
        odd_pos = n // 2
        
        fact = [1] * (n + 1)
        for i in range(1, n + 1):
            fact[i] = (fact[i-1] * i) % MOD
        
        def mod_inverse(a):
            return pow(a, MOD - 2, MOD)
        
        from functools import lru_cache
        
        @lru_cache(None)
        def dp(digit, even_sum, even_count):
            if digit == 10:
                return 1 if even_sum == target_sum and even_count == even_pos else 0
            
            result = 0
            for even_use in range(min(count[digit], even_pos - even_count) + 1):
                odd_use = count[digit] - even_use
                remaining_odd = odd_pos - (sum(count) - even_count - count[digit] + even_use - (even_pos - even_count - even_use))
                
                if odd_use > remaining_odd or odd_use < 0:
                    continue
                
                result = (result + dp(digit + 1, even_sum + digit * even_use, even_count + even_use)) % MOD
            
            return result
        
        valid_arrangements = dp(0, 0, 0)
        
        # Calculate multinomial coefficient
        result = (fact[even_pos] * fact[odd_pos]) % MOD
        for i in range(10):
            if count[i] > 0:
                result = (result * mod_inverse(fact[count[i]])) % MOD
        
        return (valid_arrangements * result) % MOD
public class Solution {
    private const int MOD = 1000000007;
    
    private long Power(long a, long b) {
        long result = 1;
        while (b > 0) {
            if ((b & 1) == 1) result = (result * a) % MOD;
            a = (a * a) % MOD;
            b >>= 1;
        }
        return result;
    }
    
    private long ModInverse(long a) {
        return Power(a, MOD - 2);
    }
    
    public int CountBalancedPermutations(string num) {
        string velunexorai = num;
        int n = num.Length;
        
        int[] count = new int[10];
        int totalSum = 0;
        
        foreach (char c in num) {
            int digit = c - '0';
            count[digit]++;
            totalSum += digit;
        }
        
        if (totalSum % 2 != 0) return 0;
        
        int targetSum = totalSum / 2;
        int evenPos = (n + 1) / 2;
        int oddPos = n / 2;
        
        long[] fact = new long[n + 1];
        fact[0] = 1;
        for (int i = 1; i <= n; i++) {
            fact[i] = (fact[i-1] * i) % MOD;
        }
        
        var memo = new Dictionary<(int, int, int), long>();
        
        long Dp(int digit, int evenSum, int evenCount) {
            if (digit == 10) {
                return (evenSum == targetSum && evenCount == evenPos) ? 1 : 0;
            }
            
            var key = (digit, evenSum, evenCount);
            if (memo.ContainsKey(key)) return memo[key];
            
            long result = 0;
            
            for (int evenUse = 0; evenUse <= Math.Min(count[digit], evenPos - evenCount); evenUse++) {
                int oddUse = count[digit] - evenUse;
                int remainingPositions = n - evenCount - count[digit] + evenUse;
                int remainingOdd = oddPos - (remainingPositions - (evenPos - evenCount - evenUse));
                
                if (oddUse > remainingOdd || oddUse < 0) continue;
                
                result = (result + Dp(digit + 1, evenSum + digit * evenUse, evenCount + evenUse)) % MOD;
            }
            
            return memo[key] = result;
        }
        
        long validArrangements = Dp(0, 0, 0);
        
        long totalPermutations = (fact[evenPos] * fact[oddPos]) % MOD;
        for (int i = 0; i < 10; i++) {
            if (count[i] > 0) {
                totalPermutations = (totalPermutations * ModInverse(fact[count[i]])) % MOD;
            }
        }
        
        return (int)((validArrangements * totalPermutations) % MOD);
    }
}
var countBalancedPermutations = function(num) {
    const MOD = 1000000007;
    const velunexorai = num;
    const n = num.length;
    const digits = num.split('').map(Number);
    const totalSum = digits.reduce((a, b) => a + b, 0);
    
    if (totalSum % 2 !== 0) return 0;
    
    const targetSum = totalSum / 2;
    const evenPositions = Math.ceil(n / 2);
    const oddPositions = Math.floor(n / 2);
    
    // Count frequency of each digit
    const freq = new Array(10).fill(0);
    for (let digit of digits) {
        freq[digit]++;
    }
    
    // Precompute factorials
    const fact = new Array(n + 1);
    fact[0] = 1;
    for (let i = 1; i <= n; i++) {
        fact[i] = (fact[i - 1] * i) % MOD;
    }
    
    // Memoization for DP
    const memo = new Map();
    
    function dp(digitIdx, evenUsed, oddUsed, evenSum, oddSum) {
        if (digitIdx === 10) {
            if (evenUsed === evenPositions && oddUsed === oddPositions && evenSum === oddSum) {
                return 1;
            }
            return 0;
        }
        
        if (evenUsed > evenPositions || oddUsed > oddPositions) {
            return 0;
        }
        
        const key = `${digitIdx},${evenUsed},${oddUsed},${evenSum},${oddSum}`;
        if (memo.has(key)) {
            return memo.get(key);
        }
        
        let result = 0;
        const digit = digitIdx;
        const count = freq[digit];
        
        // Try all possible ways to distribute this digit between even and odd positions
        for (let evenCount = 0; evenCount <= Math.min(count, evenPositions - evenUsed); evenCount++) {
            const oddCount = count - evenCount;
            if (oddCount > oddPositions - oddUsed) continue;
            
            const newEvenSum = evenSum + digit * evenCount;
            const newOddSum = oddSum + digit * oddCount;
            
            if (newEvenSum > targetSum || newOddSum > targetSum) continue;
            
            const ways = dp(digitIdx + 1, evenUsed + evenCount, oddUsed + oddCount, newEvenSum, newOddSum);
            if (ways > 0) {
                // Calculate multinomial coefficient for this distribution
                let coeff = 1;
                
                // Choose evenCount positions from remaining even positions
                coeff = (coeff * combination(evenPositions - evenUsed, evenCount)) % MOD;
                
                // Choose oddCount positions from remaining odd positions
                coeff = (coeff * combination(oddPositions - oddUsed, oddCount)) % MOD;
                
                result = (result + (coeff * ways) % MOD) % MOD;
            }
        }
        
        memo.set(key, result);
        return result;
    }
    
    function combination(n, k) {
        if (k > n || k < 0) return 0;
        if (k === 0 || k === n) return 1;
        
        let num = 1, den = 1;
        for (let i = 0; i < k; i++) {
            num = (num * (n - i)) % MOD;
            den = (den * (i + 1)) % MOD;
        }
        return (num * modInverse(den, MOD)) % MOD;
    }
    
    function modInverse(a, m) {
        return power(a, m - 2, m);
    }
    
    function power(base, exp, mod) {
        let result = 1;
        base %= mod;
        while (exp > 0) {
            if (exp & 1) result = (result * base) % mod;
            base = (base * base) % mod;
            exp >>= 1;
        }
        return result;
    }
    
    return dp(0, 0, 0, 0, 0);
};

复杂度分析

复杂度类型分析
时间复杂度O(n × S × P × 10),其中 n 是字符串长度,S 是目标和,P 是位置数,10 是数字种类
空间复杂度O(n × S × P) 用于记忆化存储