leetcode
leetcode 2651 ~ 2700
最大为 N 的数字组合

最大为 N 的数字组合

难度:

标签:

题目描述

Given an array of digits which is sorted in non-decreasing order. You can write numbers using each digits[i] as many times as we want. For example, if digits = ['1','3','5'], we may write numbers such as '13', '551', and '1351315'.

Return the number of positive integers that can be generated that are less than or equal to a given integer n.

 

Example 1:

Input: digits = ["1","3","5","7"], n = 100
Output: 20
Explanation: 
The 20 numbers that can be written are:
1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77.

Example 2:

Input: digits = ["1","4","9"], n = 1000000000
Output: 29523
Explanation: 
We can write 3 one digit numbers, 9 two digit numbers, 27 three digit numbers,
81 four digit numbers, 243 five digit numbers, 729 six digit numbers,
2187 seven digit numbers, 6561 eight digit numbers, and 19683 nine digit numbers.
In total, this is 29523 integers that can be written using the digits array.

Example 3:

Input: digits = ["7"], n = 8
Output: 1

 

Constraints:

  • 1 <= digits.length <= 9
  • digits[i].length == 1
  • digits[i] is a digit from '1' to '9'.
  • All the values in digits are unique.
  • digits is sorted in non-decreasing order.
  • 1 <= n <= 109

代码结果

运行时间: 24 ms, 内存: 16.0 MB


/*
 * 思路:
 * 1. 先将目标数n转换为字符串,以便逐位比较。
 * 2. 使用递归方法生成所有可能的小于或等于n的数字组合。
 * 3. 递归方法中每次尝试在当前数字后面添加一个新数字,若新数字大于n则停止递归。
 * 4. 使用Java Stream进行递归遍历。
 */
import java.util.*;

public class Solution {
    public int atMostNGivenDigitSet(String[] digits, int n) {
        String nStr = String.valueOf(n);
        return countNumbers(Arrays.stream(digits), nStr, true);
    }

    private int countNumbers(Stream<String> digitStream, String nStr, boolean isPrefix) {
        int count = isPrefix ? 0 : 1;
        if (nStr.isEmpty()) {
            return count;
        }
        List<String> digits = digitStream.toList();
        count += digits.stream()
            .mapToInt(digit -> {
                if (digit.charAt(0) < nStr.charAt(0)) {
                    return (int) Math.pow(digits.size(), nStr.length() - 1);
                } else if (digit.charAt(0) == nStr.charAt(0)) {
                    return countNumbers(digits.stream(), nStr.substring(1), true);
                } else {
                    return 0;
                }
            })
            .sum();
        return count;
    }
}

解释

方法:

这个解法使用动态规划来计算能生成的小于或等于n的数字的个数。首先,将n转换为字符串S以便逐位比较。创建一个dp数组,其中dp[i]表示使用digits中的数字,生成的数字小于S的第i位到最后一位的子字符串。遍历每一位数字,比较digits中的每个元素与S的当前位。如果digit小于S的当前位,则可以自由地选择剩余的位,其个数是len(digits)的(K-i-1)次幂。如果digit等于S的当前位,则当前位固定,剩余位的选择数等于dp[i+1]。最终,dp[0]加上所有小于K位数的组合数给出答案。

时间复杂度:

O(K * len(digits))

空间复杂度:

O(K)

代码细节讲解

🦆
在计算小于n的数字的个数时,解法中提到的dp数组是如何初始化的,特别是dp[K]为什么初始化为1?
在这个解法中,dp数组用于存储从某一位开始到数字n的末尾所能组成的数字数量。dp[K]初始化为1的原因是它代表了一个边界条件,即当我们考虑的数字长度正好等于n的长度时,之后不再有数字可以添加。在这种情况下,dp[K]作为一个加法单位(即加0的效果)被使用,它代表了末尾没有更多数字可以添加的情况。因此,当我们在计算比较时,如果digit等于S的最后一位,我们会将dp[K](即1)加入计算,表示这种匹配情况确实存在一种可能。
🦆
解法中提到如果digit小于当前位S[i],可以自由选择剩余的位,这里的逻辑是如何确保不会超过n的值的?
当digit小于当前位S[i]时,意味着无论在这一位之后的位数选择什么数字,组成的数都会小于n。在这种情况下,我们可以自由地为所有剩余的位选择digits中的任何数字。这里的逻辑是基于这样一个事实:由于当前位已经保证了比n的相应位小,因此后面的位无论如何填充,构成的数字都不会超过n。例如,如果n是234,当前位我们选择了1(假设digit中有1),那么后续位无论是00、01、99等,组成的数(100、101、199等)都不会超过234。
🦆
为何在计算digit等于S[i]的情况时,只是简单地加上dp[i+1],这是否考虑了所有可能的更小的位数组合?
当digit等于S[i]时,意味着当前位数与n在这一位上完全相同,因此我们不能自由选择任何更大的数字,而必须依赖于下一位的数字组合来确保不超过n。dp[i+1]存储的是从下一位开始的所有可能的数字组合数。通过添加dp[i+1],我们实际上是将这一位固定在与n相同的数字,然后考虑所有由后续位构成的可能数字。因此,这种方法确实考虑了所有在保证当前位数与n相同的情况下,由更小的位数组合可能产生的所有数字。

相关问题