LeetCode 0902. 最大为 N 的数字组合「抽象出了函数,看着较为明白的代码 + 手推」

103 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

【LetMeFly】902.最大为 N 的数字组合「抽象出了函数,看着较为明白的代码 + 手推」

力扣题目链接:leetcode.cn/problems/nu…

给定一个按 非递减顺序 排列的数字数组 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

方法一:排列组合 + 动态规划

有两种数字小于nn

  1. 数字位数直接小于nn
  2. 数字位数和nn相同,但仍然小于nn

长度短的数字

对于第一种情况,假设n=1024n=1024,那么所有的三位数都小于nn

假设候选数字digits=2,5digits = {2, 5}(有22个),那么:

  • 个位数有21=22^1=2
  • 两位数有22=42^2=4
  • 三位数有23=82^3=8

所有的三位数有2+4+8=142+4+8=14

长度和nn相等的数字

假设n=631n=631digits=2,6,7digits = {2, 6, 7}

怎么计算长度为33的数字中,小于nn的有多少个呢?

这里可以借助动态规划的思想,用两个变量lessThanlessThanequalequal,分别代表遍历到631631的某一位(记为ii)时,“小于”和“等于”631631ii位的ii位数的个数。

说人话就是:假如当前遍历到了631631的第22位(第一个数是66,第二个数是33

那么lessThanlessThan就是小于6363的两位数,equalequal就是等于6363的两位数。

最终遍历完631631的每一位后,lessThan+equallessThan + equal即为小于等于631631的三位数

  1. 首先看631631的前1166
    • 小于6611位数有一个(22),因此lessThan=1lessThan = 1
    • 等于6611位数有一个(66),因此equal=1equal = 1
  2. 接着看631631的前226363
    • 小于636322位数有44个(lessThan=lessThan×len(digits)+equallessThanThisWei=1×3+1×1=4lessThan = lessThan \times len(digits) + equal * lessThanThisWei = 1 \times 3 + 1\times 1 = 4,小于6363的包括第一位就小于6,这一位任意第一位等于6,这一位必须小于3,而小于33的数有11个),因此lessThan=4lessThan = 4
    • 等于636322位数有00个(equal=equal×equalThisWei=1 times0=0equal = equal\times equalThisWei = 1\ times 0 = 0,等于6363的方案数为第一位等于6的方案数×这一位等于3的方案数第一位等于6的方案数\times这一位等于3的方案数),因此equal=0equal = 0
  3. 最后看631631的前33631631
    • 小于63163133位数有1212个(lessThan=lessThan×len(digits)+equallessThanThisWei=4×3+0×0=12lessThan = lessThan \times len(digits) + equal * lessThanThisWei = 4 \times 3 + 0\times 0 = 12,而小于11的数有00个),因此lessThan=12lessThan = 12
    • 等于63163133位数有00个(equal=equal×equalThisWei=0 times0=0equal = equal\times equalThisWei = 0\ times 0 = 0),因此equal=0equal = 0

因此小于等于631631的三位数有lessThan+equal=12+0=12lessThan + equal = 12 + 0 = 12

(加上一位数33个和两位数3×3=93\times3=9个,由[2, 6, 7]组成的小于等于631631的数一共有12+(3+9)=2412+(3+9)=24个)

  • 时间复杂度O((log10n)×(log10n+len(digits)))O((\log_{10}n)\times(\log_{10}n + len(digits)))。前面求“短数字”的时间复杂度是O((log10n)2)O((\log_{10}n)^2),后面求“等长数字”的时间复杂度是O(log10n×len(digits))O(\log_{10}n\times len(digits))(这里题目中说digitsdigits是升序的,因此还可以实用二分查找,但是数据量不大,因此不是很有必要)
  • 空间复杂度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;
    }
};

result

同步发文于CSDN,原创不易,转载请附上原文链接哦~ Tisfy:letmefly.blog.csdn.net/article/det…