给定按升序排列、互不相同的数字字符数组 digits 和整数 n,统计可以用 digits 中数字重复组合出的、范围在 [1, n] 内的正整数个数。
长度为 1 的合法数有 4 个,长度为 2 的有 4² = 16 个;无法组成不超过 100 的三位数,所以共 20 个。
不能真的生成所有组合。应先按位数计数,再处理与 n 位数相同的数字:
n 少的数字一定小于 n,可直接用乘法原理统计。n 等长时,从最高位到最低位逐位比较。这是数位计数,也可以视作数位 DP 的简化形式。
设 n 有 length 位,可选数字有 digitCount 个。
依次枚举数字长度:
题目保证 digits 不包含 "0",所以每一位都可以从 digitCount 个数字中任意选择。长度为 len 的数字共有:
这些数字的位数少于 n,因此一定小于 n,可以全部计入答案。
从 n 的最高位开始逐位处理。进入第 position 位时,始终满足:
比较当前位时有三种选择:
n 当前位的数字:整个数字已经确定小于 n,剩余位可以任意选择。n 当前位的数字:前缀继续保持相等,进入下一位比较。n 当前位的数字:整个数字一定大于 n,不能计入答案。如果 digits 中不存在等于当前位的数字,那么所有更小的选择都已经统计,更大的选择又不合法,当前相等前缀无法继续,可以直接返回。
如果 n 的每一位都能在 digits 中找到相同数字,说明 n 本身也能组成,最后需要额外加 1。
digits = ["1","3","5","7"],n = 365:
365 是三位数。整个统计过程分成两部分:先统计一位数和两位数,再单独统计不超过 365 的三位数。
一位数有 4 个:
两位数的十位和个位都可以从 digits 中任意选择,所以有:
这些数字都不足三位,一定小于 365,因此先得到:
三位数必须从百位开始与 365 比较。这里要抓住一个关键点:
n 的对应位小,整个数字就已经确定小于 n,后面的位可以任意选择。n 的对应位相等,还不能确定大小,需要继续比较下一位。n 的对应位大,整个数字一定大于 n,不能计入。365 的百位是 3,可选数字是 1、3、5、7:
1:百位满足 1 < 3,所以所有 1xx 都小于 365。剩余十位、个位各有 4 种选择,共有 1 × 4² = 16 个。3:百位与 365 相等,暂时不能计数,需要继续比较十位。5 或 7:百位已经大于 3,组成的数一定大于 365,不能计入。百位选择 1 时得到的 16 个数字是:
到这里累计有:
只有百位选择 3 的分支还需要继续,也就是说当前已经匹配了前缀 3。365 的十位是 6:
1、3、5:它们都小于 6,所以 31x、33x、35x 一定小于 365。个位可以任意选择,共有 3 × 4¹ = 12 个。7:十位大于 6,组成的数一定大于 365,不能计入。digits 中没有 6:无法让十位继续与 365 相等,因此相等分支到这里结束,不需要再比较个位。这 12 个数字是:
最终统计结果为:
代码中的:
就是在计算“当前位选择得更小后,剩余位置可以任意选择”的组合数。比较百位时还剩两位,所以增加 4²;比较十位时还剩一位,所以每个更小的十位选择增加 4¹。
如果题目不要求统计个数,而是要求从 digits 中重复选择数字,组成一个不大于 n 的最大数字,思路会从“计数”变成“构造”。
核心策略是:尽量让结果和 n 的前缀相同;一旦某一位必须选更小的数字,后面的所有位都填 digits 中最大的数字。
处理时仍然从高位到低位比较:
n 当前位的数字,就先保持相等,继续看下一位。n 等长的合法数字,只能返回位数少一位、全部由最大数字组成的数。例如 digits = ["1","3","5","7"],n = 365:
再看一个需要回退的例子,digits = ["1","3","5","7"],n = 300:
这里用递归代替手动回退:build(position) 返回 null 时,调用它的上一层自然会走到“选更小数字”的分支,调用栈本身就保存了前缀,不需要额外的数组和反向循环。
这段代码返回的是数字。如果不存在任何合法正整数,例如 digits = ["5"]、n = 3,返回 null。如果担心结果超过 JavaScript 安全整数范围,可以保留字符串结果,不做 Number(...) 转换。
L 为 n 的位数、D 为 digits.length,时间复杂度为 O(L × D),也就是 O(log n × digits.length)。O(L),用于保存 String(n)。digitCount ** len 成立的前提是 digits 不包含 "0";否则最高位不能为 0,需要单独处理前导零。digit 和 target[position] 都是单个数字字符,"1" 到 "9" 的字典序与数值顺序一致,因此可以直接比较。n 相等;一旦当前位选择了更小的数字,剩余位就可以任意组合并一次性计数。n 的每一位时,才能把 n 本身计入。