当前位置: 首页 > java >正文

LeetCode难题解析:数字字符串的平衡排列数目

问题描述

给定一个数字字符串 num,统计其所有不同排列中满足以下条件的数目:
奇数位下标数字之和 = 偶数位下标数字之和(下标从0开始)。
答案需对 109+7109+7 取模。

示例
输入:num = "08143"
输出:12
解释:存在12种排列方式使得奇数位和与偶数位和相等。


初探问题:暴力法的局限

思路分析

最直观的想法是生成所有排列,逐一检查是否满足条件。然而,当字符串长度 nn 较大时,排列数为 O(n!)O(n!),显然会超时。

代码示例(Java)

java

// 暴力回溯(仅示意,不可行)
public class BruteForceSolution {private int count = 0;public int countBalancedPermutations(String num) {backtrack(num.toCharArray(), 0);return count;}private void backtrack(char[] arr, int start) {if (start == arr.length) {if (isBalanced(arr)) count++;return;}for (int i = start; i < arr.length; i++) {swap(arr, start, i);backtrack(arr, start + 1);swap(arr, start, i);}}private boolean isBalanced(char[] arr) {int evenSum = 0, oddSum = 0;for (int i = 0; i < arr.length; i++) {if (i % 2 == 0) evenSum += arr[i] - '0';else oddSum += arr[i] - '0';}return evenSum == oddSum;}
}

缺点:时间复杂度 O(n!)O(n!),无法处理 n>10n>10 的情况。


优化思路:组合数学 + 动态规划

关键观察

  1. 总和奇偶性:若所有数字之和为奇数,直接返回0。

  2. 分配问题:将数字分配到奇数位和偶数位,使得两部分和相等。

数学建模

设总共有 mm 个奇数位和 kk 个偶数位(m=⌈n/2⌉m=⌈n/2⌉, k=⌊n/2⌋k=⌊n/2⌋)。
对每个数字 dd,选择 jj 个放在奇数位,剩余 cnt[d]−jcnt[d]−j 个放在偶数位,需满足:

  • ∑(jd×d)=总和2∑(jd​×d)=2总和​

  • ∑jd=m∑jd​=m

动态规划状态定义

定义 dfs(pos, currSum, oddRemain)

  • pos:当前处理的数字(0~9)

  • currSum:奇数位当前总和

  • oddRemain:剩余可分配的奇数位数量

转移方程

对每个数字 dd,枚举分配到奇数位的数量 jj,满足:
j∈[max⁡(0,cnt[d]−剩余偶数位),min⁡(cnt[d],oddRemain)]j∈[max(0,cnt[d]−剩余偶数位),min(cnt[d],oddRemain)]
方案数为组合数乘积:
C(oddRemain,j)×C(剩余偶数位,cnt[d]−j)C(oddRemain,j)×C(剩余偶数位,cnt[d]−j)


高效实现:预计算与记忆化

预计算组合数

使用动态规划预计算组合数 C(n,k)mod  MODC(n,k)modMOD,避免重复计算。

java

// 预处理组合数C(n,k)
comb = new long[maxOdd + 1][maxOdd + 1];
for (int i = 0; i <= maxOdd; i++) {comb[i][0] = 1;for (int j = 1; j <= i; j++) {comb[i][j] = (comb[i-1][j] + comb[i-1][j-1]) % MOD;}
}

记忆化搜索

通过三维数组 memo[pos][currSum][oddRemain] 记录状态,减少重复计算。

java

private long dfs(int pos, int currSum, int oddRemain) {if (memo[pos][currSum][oddRemain] != -1) return memo[pos][currSum][oddRemain];// ... 递归逻辑 ...memo[pos][currSum][oddRemain] = res;return res;
}

剪枝优化

  • 若当前总和超过目标值 target,提前返回。

  • 若剩余奇数位数量不足,跳过非法状态。


完整代码实现

java

import java.util.Arrays;public class Solution {private static final long MOD = 1_000_000_007;private long[][][] memo;private int[] cnt;private int target;private long[][] comb;private int[] psum;public int countBalancedPermutations(String num) {// 统计数字频率并计算总和cnt = new int[10];int tot = 0, n = num.length();for (char c : num.toCharArray()) {int d = c - '0';cnt[d]++;tot += d;}if (tot % 2 != 0) return 0;target = tot / 2;int maxOdd = (n + 1) / 2;// 预处理组合数comb = new long[maxOdd + 1][maxOdd + 1];for (int i = 0; i <= maxOdd; i++) {comb[i][0] = 1;for (int j = 1; j <= i; j++) {comb[i][j] = (comb[i-1][j] + comb[i-1][j-1]) % MOD;}}// 计算psum[i]: 从i到9的数字总数psum = new int[11];for (int i = 9; i >= 0; i--) {psum[i] = psum[i+1] + cnt[i];}// 初始化记忆化数组memo = new long[10][target+1][maxOdd+1];for (long[][] arr2 : memo) {for (long[] arr1 : arr2) {Arrays.fill(arr1, -1);}}return (int) dfs(0, 0, maxOdd);}private long dfs(int pos, int currSum, int oddRemain) {if (oddRemain < 0 || currSum > target) return 0;if (pos == 10) {return (currSum == target && oddRemain == 0) ? 1 : 0;}if (memo[pos][currSum][oddRemain] != -1) {return memo[pos][currSum][oddRemain];}long res = 0;int evenRemain = psum[pos] - oddRemain;int lower = Math.max(0, cnt[pos] - evenRemain);int upper = Math.min(cnt[pos], oddRemain);for (int j = lower; j <= upper; j++) {int k = cnt[pos] - j;if (k < 0 || k > evenRemain) continue;long ways = (comb[oddRemain][j] * comb[evenRemain][k]) % MOD;res = (res + ways * dfs(pos+1, currSum + pos*j, oddRemain - j)) % MOD;}memo[pos][currSum][oddRemain] = res;return res;}public static void main(String[] args) {Solution sol = new Solution();System.out.println(sol.countBalancedPermutations("08143")); // 输出12}
}

复杂度分析

  • 时间复杂度:O(10×T×M)O(10×T×M),其中 TT 为目标和,MM 为最大奇数位数量。

  • 空间复杂度:O(10×T×M)O(10×T×M),用于存储记忆化状态。


总结

  1. 组合数学:将排列问题转化为数字分配问题。

  2. 动态规划:通过状态压缩减少计算量。

  3. 预计算与剪枝:显著提升效率的关键技巧。

拓展思考:如何将问题推广到更多进制或更复杂的平衡条件?欢迎在评论区讨论!


希望这篇文章能帮助你深入理解该问题的解决思路!

http://www.xdnf.cn/news/5035.html

相关文章:

  • 阻焊工艺如何保障多层PCB可靠性?5大核心功能与工艺控制要点
  • 深入理解 Istio 的工作原理 v1.26.0
  • 计算机网络:深度解析基于链路状态的内部网关协议IS-IS
  • OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——gmp
  • 赛季7靶场 - Environment
  • 死锁的形成
  • 国产Excel处理控件Spire.XLS系列教程:C# 将Excel文件转换为Markdown格式
  • 线程邮箱框架与示例
  • 《Spring Boot 3.0全新特性详解与实战案例》
  • 科学选择差分探头输入阻抗的方法
  • Liunx ContOS7 安装部署 Docker
  • RabbitMQ ②-工作模式
  • Rust 智能指针全解析:从原理到实践
  • 基于DeepSeek的韦恩图绘制:方法、优化与应用
  • NX884NX891美光固态闪存NX895NX907
  • ET2120工业Lora数传终端RS485串口4*AIAO+Moubus RTU
  • 北斗导航 | RTKLib中模糊度解算详解,公式,代码
  • 【愚公系列】《Manus极简入门》028-创业规划顾问:“创业导航仪”
  • Python - 如何打包并发布 Python 库到 PyPI
  • 运维体系架构规划
  • VBA -- 学习Day3
  • Java设计模式之抽象工厂模式:从入门到精通
  • 工业设计破局密码:3D 可视化技术点燃产业升级引擎
  • 如何将邮件送达率从60%提升到95%
  • 【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)高级用法
  • 生产安全管理系统标杆
  • 【python】Calculate the Angle of a Triangle
  • 大物重修之浅显知识点
  • ch09 课堂参考代码
  • 【MySQL】数据库的数据类型