【LetMeFly】902.最大为 N 的数字组合「抽象出了函数,看着较为明白的代码 + 手推」
力扣题目链接:https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/
给定一个按 非递减顺序 排列的数字数组 digits
。你可以用任意次数 digits[i]
来写的数字。例如,如果 digits = ['1','3','5']
,我们可以写数字,如 '13'
, '551'
, 和 '1351315'
。
返回 可以生成的小于或等于给定整数 n
的正整数的个数 。
示例 1:
输入:digits = ["1","3","5","7"], n = 100 输出:20 解释: 可写出的 20 个数字是: 1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.
示例 2:
输入:digits = ["1","4","9"], n = 1000000000 输出:29523 解释: 我们可以写 3 个一位数字,9 个两位数字,27 个三位数字, 81 个四位数字,243 个五位数字,729 个六位数字, 2187 个七位数字,6561 个八位数字和 19683 个九位数字。 总共,可以使用D中的数字写出 29523 个整数。
示例 3:
输入:digits = ["7"], n = 8 输出:1
提示:
1 <= digits.length <= 9
digits[i].length == 1
digits[i]
是从'1'
到'9'
的数digits
中的所有值都 不同digits
按 非递减顺序 排列1 <= n <= 109
方法一:排列组合 + 动态规划
有两种数字小于 n n n:
- 数字位数直接小于 n n n的
- 数字位数和 n n n相同,但仍然小于 n n n
长度短的数字
对于第一种情况,假设 n = 1024 n=1024 n=1024,那么所有的三位数都小于 n n n
假设候选数字 d i g i t s = 2 , 5 digits = {2, 5} digits=2,5(有 2 2 2个),那么:
- 个位数有 2 1 = 2 2^1=2 21=2个
- 两位数有 2 2 = 4 2^2=4 22=4个
- 三位数有 2 3 = 8 2^3=8 23=8个
所有的三位数有 2 + 4 + 8 = 14 2+4+8=14 2+4+8=14个
长度和 n n n相等的数字
假设 n = 631 n=631 n=631, d i g i t s = 2 , 6 , 7 digits = {2, 6, 7} digits=2,6,7
怎么计算长度为 3 3 3的数字中,小于 n n n的有多少个呢?
这里可以借助动态规划的思想,用两个变量 l e s s T h a n lessThan lessThan和 e q u a l equal equal,分别代表遍历到 631 631 631的某一位(记为 i i i)时,“小于”和“等于” 631 631 631前 i i i位的 i i i位数的个数。
说人话就是:假如当前遍历到了 631 631 631的第 2 2 2位(第一个数是 6 6 6,第二个数是 3 3 3)
那么 l e s s T h a n lessThan lessThan就是小于 63 63 63的两位数, e q u a l equal equal就是等于 63 63 63的两位数。
最终遍历完 631 631 631的每一位后, l e s s T h a n + e q u a l lessThan + equal lessThan+equal即为小于等于 631 631 631的三位数
- 首先看 631 631 631的前 1 1 1位 6 6 6:
- 小于 6 6 6的 1 1 1位数有一个( 2 2 2),因此 l e s s T h a n = 1 lessThan = 1 lessThan=1
- 等于 6 6 6的 1 1 1位数有一个( 6 6 6),因此 e q u a l = 1 equal = 1 equal=1
- 接着看 631 631 631的前 2 2 2位 63 63 63:
- 小于 63 63 63的 2 2 2位数有 4 4 4个( l e s s T h a n = l e s s T h a n × l e n ( d i g i t s ) + e q u a l ∗ l e s s T h a n T h i s W e i = 1 × 3 + 1 × 1 = 4 lessThan = lessThan \times len(digits) + equal * lessThanThisWei = 1 \times 3 + 1\times 1 = 4 lessThan=lessThan×len(digits)+equal∗lessThanThisWei=1×3+1×1=4,小于 63 63 63的包括
第一位就小于6,这一位任意
和第一位等于6,这一位必须小于3
,而小于 3 3 3的数有 1 1 1个),因此 l e s s T h a n = 4 lessThan = 4 lessThan=4 - 等于 63 63 63的 2 2 2位数有 0 0 0个( e q u a l = e q u a l × e q u a l T h i s W e i = 1 t i m e s 0 = 0 equal = equal\times equalThisWei = 1\ times 0 = 0 equal=equal×equalThisWei=1 times0=0,等于 63 63 63的方案数为 第一位等于 6 的方案数 × 这一位等于 3 的方案数 第一位等于6的方案数\times这一位等于3的方案数 第一位等于6的方案数×这一位等于3的方案数),因此 e q u a l = 0 equal = 0 equal=0
- 小于 63 63 63的 2 2 2位数有 4 4 4个( l e s s T h a n = l e s s T h a n × l e n ( d i g i t s ) + e q u a l ∗ l e s s T h a n T h i s W e i = 1 × 3 + 1 × 1 = 4 lessThan = lessThan \times len(digits) + equal * lessThanThisWei = 1 \times 3 + 1\times 1 = 4 lessThan=lessThan×len(digits)+equal∗lessThanThisWei=1×3+1×1=4,小于 63 63 63的包括
- 最后看 631 631 631的前 3 3 3位 631 631 631:
- 小于 631 631 631的 3 3 3位数有 12 12 12个( l e s s T h a n = l e s s T h a n × l e n ( d i g i t s ) + e q u a l ∗ l e s s T h a n T h i s W e i = 4 × 3 + 0 × 0 = 12 lessThan = lessThan \times len(digits) + equal * lessThanThisWei = 4 \times 3 + 0\times 0 = 12 lessThan=lessThan×len(digits)+equal∗lessThanThisWei=4×3+0×0=12,而小于 1 1 1的数有 0 0 0个),因此 l e s s T h a n = 12 lessThan = 12 lessThan=12
- 等于 631 631 631的 3 3 3位数有 0 0 0个( e q u a l = e q u a l × e q u a l T h i s W e i = 0 t i m e s 0 = 0 equal = equal\times equalThisWei = 0\ times 0 = 0 equal=equal×equalThisWei=0 times0=0),因此 e q u a l = 0 equal = 0 equal=0
因此小于等于 631 631 631的三位数有 l e s s T h a n + e q u a l = 12 + 0 = 12 lessThan + equal = 12 + 0 = 12 lessThan+equal=12+0=12个
(加上一位数 3 3 3个和两位数 3 × 3 = 9 3\times3=9 3×3=9个,由[2, 6, 7]
组成的小于等于 631 631 631的数一共有 12 + ( 3 + 9 ) = 24 12+(3+9)=24 12+(3+9)=24个)
- 时间复杂度 O ( ( log 10 n ) × ( log 10 n + l e n ( d i g i t s ) ) ) O((\log_{10}n)\times(\log_{10}n + len(digits))) O((log10n)×(log10n+len(digits)))。前面求“短数字”的时间复杂度是 O ( ( log 10 n ) 2 ) O((\log_{10}n)^2) O((log10n)2),后面求“等长数字”的时间复杂度是 O ( log 10 n × l e n ( d i g i t s ) ) O(\log_{10}n\times len(digits)) O(log10n×len(digits))(这里题目中说 d i g i t s digits digits是升序的,因此还可以实用二分查找,但是数据量不大,因此不是很有必要)
- 空间复杂度 O ( 1 ) O(1) O(1)
AC代码
C++
class Solution {
private:
/* 字符c是否在digits中 */
bool isIn(char c, vector<string>& digits) {
for (string& s : digits) {
if (c == s[0])
return true;
}
return false;
}
/* digits中小于字符c的元素的个数 */
int cntLessThan(char c, vector<string>& digits) {
int ans = 0;
for (string& s : digits) {
if (s[0] < c)
ans++;
}
return ans;
}
public:
int atMostNGivenDigitSet(vector<string>& digits, int n) {
int ans = 0;
// 求“短数字”
int len = to_string(n).size();
for (int i = 1; i < len; i++) {
ans += pow(digits.size(), i);
}
// 求“等长数字”
string strify = to_string(n);
int lessThan = cntLessThan(strify[0], digits), equal = isIn(strify[0], digits); // 实用常数空间
for (int i = 1; i < len; i++) {
lessThan = lessThan * digits.size() + equal * cntLessThan(strify[i], digits); // 公式原理在“631”的举例中详细说明了
equal = equal * isIn(strify[i], digits);
}
ans += lessThan + equal;
return ans;
}
};
同步发文于CSDN,原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/127385999