【每日一题】:902 最大为 N 的数字组合

64 阅读3分钟

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

今日的LeetCode每日一题是902. 最大为 N 的数字组合 - 力扣(LeetCode)。该题目如果第一次接触会感觉可以用暴力搜索来解决,但是其实是一道动态规划的题目。

题目内容

给定一个按 非递减顺序 排列的数字数组digits 。你可以用任意次数digits[i]来写的数字。例如,如果 digits = ['1','3','5'],我们可以写数字,如 '13''551', 和 '1351315'

问:返回可以生成的小于或等于给定整数 n 的正整数的个数。

样例

输入: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.

输入:digits = ["1","4","9"], n = 1000000000
输出:29523

方法

通过观察题目可以知道,在给定的数字范围内,我们需要求解出满足特定条件正整数的个数

  1. 满足条件:生成的小于或等于给定整数n的数字
  2. 查找范围:只能由给定的数字所构成

一般情况下,通常我们采用暴力搜索的方法去组合出所有的顺序并且将满足的条件的结果进行统计,但是往往会因为数字的位数过多而超出整数范围而不方便进行统计。因此我们可以对查找范围条件加以分析,可以发现在查找范围中数字,有很多位都是重复的。比如13332333后面都是相同的,只有最高位存在不同。

因此可以利用这种具有重叠子问题的性质来解决这道题,动态规划就是最好的方法。在动态规划方法中,首先就是要确定动态转移方程,动态转移方程是对求解重叠子问题的一种抽象,即:

完整问题 = 上一阶段问题的答案(子问题) + 当前阶段的答案

比如在本题中,我们要对满足n=1234的数字进行统计,可以将求解n的问题拆解成求解子问题 (n = 1, n = 12, n = 123, n = 1234)的情形。通过递推的方式,逐步从求解简单情况到求解复杂情况。

假设求解的n是一个十进制的k位数,可以定义如下:

  • dp[i][0] 表示的是由digits构成,并且小于n的前i位的数字的个数
  • dp[i][1] 表示的是由digits构成,并且等于n的前i位的数字的个数
class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
        String s = Integer.toString(n);
        int m = digits.length, k = s.length();
        int[][] dp = new int[k + 1][2];
        dp[0][1] = 1;
        for (int i = 1; i <= k; i++) {
            for (int j = 0; j < m; j++) {
                if (digits[j].charAt(0) == s.charAt(i - 1)) {
                    dp[i][1] = dp[i - 1][1];
                } else if (digits[j].charAt(0) < s.charAt(i - 1)) {
                    dp[i][0] += dp[i - 1][1];
                } else {
                    break;
                }
            }
            if (i > 1) {
                dp[i][0] += m + dp[i - 1][0] * m;
            }
        }
        return dp[k][0] + dp[k][1];
    }
}