【LeetCode 902】最大为 N 的数字组合 - 详细题解与数位组合思路
题目描述
给定一个按升序排列的数字数组 digits 和一个整数 n,返回用 digits 中的数字组合出的、小于或等于 n 的正整数的个数。
解题思路(数位 DP / 组合计数)
我们可以将统计满足条件的数字个数分为 两大类 进行讨论:
1. 位数小于 n 的数字
如果构造的数字位数比 n 的位数(设为 lenlenlen)小,那么无论怎么选,数值一定小于 n。
- 长度为 1 的数字个数:m1m^1m1
- 长度为 2 的数字个数:m2m^2m2
- ...
- 长度为 len−1len-1len−1 的数字个数:mlen−1m^{len-1}mlen−1
我们可以直接通过循环累加 m1+m2+⋯+mlen−1m^1 + m^2 + \dots + m^{len-1}m1+m2+⋯+mlen−1。
2. 位数等于 n 的数字
将 n 转化为字符串 sss,逐位(从高位到低位 iii)进行比较:
- 当前位选择小于 sisisi 的数字 :
- 此时后续剩余的 len−1−ilen - 1 - ilen−1−i 个位置可以任意选择
digits中的数字。 - 方案增加数:m(len−1−i)m^{(len - 1 - i)}m(len−1−i)。
- 此时后续剩余的 len−1−ilen - 1 - ilen−1−i 个位置可以任意选择
- 当前位选择等于 sisisi 的数字 :
- 标记当前位可以匹配(
same = true),并继续考察下一位 i+1i+1i+1。
- 标记当前位可以匹配(
- 当前位没有可选的数字小于或等于 sisisi :
- 如果
digits中没有与 sisisi 相同的数字,说明无法继续向后构造与n相同的数,此时直接返回累加结果。
- 如果
如果成功匹配完所有的 lenlenlen 位,说明可以构造出一个完全等于 n 的数字,最终答案额外 +1+1+1。
C++ 代码实现
cpp
class Solution {
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
string s = to_string(n);
int m = digits.size();
int len = s.size();
long long ans = 0;
// 1. 统计位数小于 len 的所有组合数
long long tem = 1;
for (int i = 1; i < len; i++) {
tem *= m;
ans += tem;
}
// 2. 统计位数等于 len 且小于等于 n 的组合数
for (int i = 0; i < len; i++) {
bool same = false;
for (auto &d : digits) {
if (d[0] < s[i]) {
// 当前位选小于 s[i] 的数,后续位可任意组合
long long tem2 = 1;
for (int j = i + 1; j < len; j++) {
tem2 *= m;
}
ans += tem2;
} else if (d[0] == s[i]) {
// 找到相等的数字,标记可以继续比对下一位
same = true;
}
}
// 如果 digits 中没有与 s[i] 相等的数字,无法继续向前匹配,直接返回
if (!same) {
return ans;
}
}
// 遍历完成说明可以构造出完全等于 n 的数字
return ans + 1;
}
};
复杂度分析
- 时间复杂度 :O(len×m)\mathcal{O}(len \times m)O(len×m),其中 lenlenlen 为数字 nnn 的十进制位数(不超过 10),mmm 为
digits数组的大小。循环层数和数字位数成正比,运行耗时极低(可达 0 ms)。 - 空间复杂度 :O(len)\mathcal{O}(len)O(len),仅用于将整数 nnn 转换为字符串存储。