2026-07-17:统计满足数位和数组的非递减数组数目。用go语言,给定一个长度为 n 的整数数组 digitSum。
需要统计能构造出多少个长度为 n 的数组 arr,同时满足:
-
arr 的每一项都是 0 到 5000 之间的整数。
-
arr 是非递减的,即 arr[0] ≤ arr[1] ≤ … ≤ arr[n-1]。
-
对于每个位置 i,arr[i] 的十进制各位数字之和必须等于 digitSum[i]。
最终返回不同有效数组的个数,结果对 10⁹ + 7 取模。
1 <= digitSum.length <= 1000。
0 <= digitSum[i] <= 50。
输入: digitSum = [25,1]。
输出: 6。
解释:
数位和为 25 的数字有 799、889、898、979、988 和 997。
数位和为 1 且可以出现在这些值之后同时保持数组非递减的唯一数字是 1000。
因此,有效数组为 [799, 1000]、[889, 1000]、[898, 1000]、[979, 1000]、[988, 1000] 和 [997, 1000]。
因此,答案是 6。
题目来自力扣3883。
1. 预处理:按数位和分组数字
- 枚举从
0到5000的每一个整数x,利用递推公式digSum[x] = digSum[x/10] + (x % 10)快速算出它的十进制各位数字之和。 - 最大数位和出现在
4999,其值为4+9+9+9 = 31。因此所有数字被分到32个桶中,每个桶对应一个数位和0~31。 - 最终得到
sumToNums数组,其中sumToNums[s]存放所有数位和为s的数字,并且由于枚举顺序是从小到大,每个桶内的数字天然保持升序。这部分计算只执行一次,与输入无关。
2. 动态规划准备
- 创建一个长度为
5001的数组f,f[x]表示以数字x结尾的有效非递减数组的数量。 - 为了统一处理第一个元素,设置一个虚拟起点:令
f[0] = 1,相当于认为在前一个位置存在一个数位和为0的数字0(它小于等于任何有效数字)。这样在计算第一个位置时,可以无缝复用后续的转移逻辑。 - 变量
pre记录前一个位置的数位和,初始设为0。
3. 逐位计算有效数组数量
对输入序列 digitSum 中的每个目标数位和 cur 依次处理:
3.1 可行性检查
如果 cur > 31,说明要求的数位和不可能由 0~5000 内的数字实现,直接返回 0。
3.2 获取相关数字列表
a = sumToNums[pre]:前一个位置允许的所有数字(已按升序排列)。- 当前列表
b = sumToNums[cur]:当前位置允许的所有数字(同样升序)。
3.3 双指针累加转移
- 初始化指针
j = 0指向a的开头,累加和sum = 0。 - 遍历当前列表
b中的每一个候选数字x:- 因为
a和b都已升序,我们可以利用双指针技巧:不断右移j,将a中所有满足a[j] <= x的f[a[j]]累加到sum中。这样保证了非递减约束——前一个数字只要小于等于x就能接上。 - 当所有符合条件的
a[j]都处理完后,sum就代表了所有以x结尾的当前序列的个数。将它对10^9+7取模后赋给f[x]。
- 因为
- 处理完当前列表后,
f数组就更新为以各个x(数位和为cur)结尾的有效序列数量。
3.4 进入下一轮
令 pre = cur,为下一个位置的转移做好准备。
4. 汇总最终答案
- 遍历完所有位置后,
pre中存储的是最后一个目标数位和。 - 将所有数位和为
pre的数字x对应的f[x]累加起来,就得到了整个序列的不同有效数组总数。 - 最后再取一次模,返回结果。
复杂度分析
- 时间复杂度:
预处理部分只需遍历5001个数字,复杂度O(5000)。
动态规划部分,对每个digitSum元素,我们遍历了前一个数位和的数字列表和当前数位和的数字列表。尽管有双重循环,但双指针的内层while在每个状态转移中总共只将前一个列表扫描一遍。因此,总操作次数等于相邻两步列表长度之和的总和。所有桶的数字总数不超过5001,假设digitSum长度为n(最大1000),总时间复杂度为O(5000 + n * L_max),其中L_max是单个数位和的最大数字个数(远小于5000)。实际运行极其迅速。 - 额外空间复杂度:
全局的sumToNums保存了所有数字的分组,占用O(5000)空间;动态规划数组f同样占用O(5000)空间。除输入外,总额外空间为O(5000),即常数级,非常节省。
Go完整代码如下:
package main
import (
"fmt"
)
const mx = 5001
const maxDigitSum = 31 // 4999 的数位和最大
var sumToNums [maxDigitSum + 1][]int
func init() {
digSum := [mx]int{}
for x := range digSum {
// 去掉 x 的个位,问题变成 x/10 的数位和,即 digSum[x/10]
digSum[x] = digSum[x/10] + x%10
sumToNums[digSum[x]] = append(sumToNums[digSum[x]], x)
}
}
func countArrays(digitSum []int) (ans int) {
const mod = 1_000_000_007
f := [mx]int{1} // f[x] 表示以 x 结尾的有效数组的个数
pre := 0
for _, cur := range digitSum {
if cur > maxDigitSum {
return 0
}
a := sumToNums[pre]
j, m := 0, len(a)
sum := 0
for _, x := range sumToNums[cur] {
// 有效数组的前一个数只要 <= x 就行
for ; j < m && a[j] <= x; j++ {
sum += f[a[j]]
}
// sum 现在就是以 x 结尾的有效数组的个数
f[x] = sum % mod
}
pre = cur // 记录上一个数位和
}
for _, x := range sumToNums[pre] {
ans += f[x]
}
return ans % mod
}
func main() {
digitSum := []int{25, 1}
result := countArrays(digitSum)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
from typing import List
MOD = 1_000_000_007
MX = 5001
MAX_DIGIT_SUM = 31 # 4999 的数位和最大为 31
# 预计算:按数位和分组数字 0..5000
sum_to_nums = [[] for _ in range(MAX_DIGIT_SUM + 1)]
dig_sum = [0] * MX
for x in range(MX):
dig_sum[x] = dig_sum[x // 10] + (x % 10)
sum_to_nums[dig_sum[x]].append(x)
def countArrays(digitSum: List[int]) -> int:
f = [0] * MX
f[0] = 1 # 以 0 结尾的方案数为 1(虚起点)
pre = 0 # 上一个数位和,初始为 0
for cur in digitSum:
if cur > MAX_DIGIT_SUM:
return 0
a = sum_to_nums[pre] # 前一个数位和对应的数字列表
j = 0
m = len(a)
s = 0
for x in sum_to_nums[cur]:
# 累加所有满足 a[j] <= x 的 f[a[j]]
while j < m and a[j] <= x:
s += f[a[j]]
j += 1
f[x] = s % MOD
pre = cur # 更新前一个数位和
ans = 0
for x in sum_to_nums[pre]:
ans += f[x]
return ans % MOD
# 示例测试
if __name__ == "__main__":
digitSum = [25, 1]
print(countArrays(digitSum))
C++完整代码如下:
#include <iostream>
#include <vector>
using namespace std;
const int MX = 5001;
const int MAX_DIGIT_SUM = 31; // 4999 的数位和最大为 31
const int MOD = 1'000'000'007;
// 预计算:按数位和分组数字 0..5000(只初始化一次)
const vector<vector<int>>& getSumToNums() {
static vector<vector<int>> sumToNums = []() {
vector<vector<int>> res(MAX_DIGIT_SUM + 1);
vector<int> digSum(MX, 0);
for (int x = 0; x < MX; ++x) {
if (x > 0) digSum[x] = digSum[x / 10] + (x % 10);
res[digSum[x]].push_back(x);
}
return res;
}();
return sumToNums;
}
int countArrays(const vector<int>& digitSum) {
const auto& sumToNums = getSumToNums();
vector<int> f(MX, 0);
f[0] = 1; // 以 0 结尾的方案数为 1(虚起点)
int pre = 0; // 上一个数位和,初始为 0
for (int cur : digitSum) {
if (cur > MAX_DIGIT_SUM) return 0;
const auto& a = sumToNums[pre];
int j = 0, m = a.size();
long long sum = 0; // 用 64 位避免溢出
for (int x : sumToNums[cur]) {
// 累加所有满足 a[j] <= x 的 f[a[j]]
while (j < m && a[j] <= x) {
sum += f[a[j]];
++j;
}
f[x] = sum % MOD;
}
pre = cur;
}
long long ans = 0;
for (int x : sumToNums[pre]) {
ans += f[x];
}
return ans % MOD;
}
int main() {
vector<int> digitSum = {25, 1};
cout << countArrays(digitSum) << endl;
return 0;
}