Leetcode 902. Numbers At Most N Given Digit Set

Problem Explanation:

We have a subset of the digits from 1 to 9. Our task is to find how many numbers that are less than or equal to N can be formed using these digits. We are allowed to use each digit in the subset as many times as possible and the numbers can be as long as we wish.

We go through an example to better understand the problem:

For example, if our subset D is ["1","3","5","7"], and N = 100. The numbers that can be written using the subset that are less than or equal to N are: 1, 3, 5, 7, 11, 13, 15, 17, 31, 33, 35, 37, 51, 53, 55, 57, 71, 73, 75, 77. So the total is 20.

Approach Explanation:

The solution to this problem involves a two step process.

First, count the number of integers that have a length less than the length of N since all of them will be less than N. We can do this by calculating the power of the number of digits for all lengths less than the length of N and summing these up.

Next, we loop over each digit in N (from most significant to least significant digit).

With each digit, we increase our count by the number of possible numbers we can form that are guaranteed to be less than N. This is done by counting all the digits in our set that are less than the current digit and multiplying this with the maximum number of possible integers we can form with the remaining digits.

If we encounter a digit in N that does not exist in our set, we can stop as any number made with the previous digits and digits from our set will be less than N.

And at the very end, we add one since N can always be formed by digits from the set.

Let's walk through the Python, Java, JavaScript, C++ and C# solutions using this approach:

Python Solution

1
2Python
3class Solution:
4    def atMostNGivenDigitSet(self, digits, n):
5        num_str = str(n)
6        digits = sorted(digits)
7        dp = [0] * len(num_str) + [1]
8        
9        for i in range(len(num_str) - 1, -1, -1):
10            for d in digits:
11                if num_str[i] > d:
12                    dp[i] += len(digits) ** (len(num_str) - i - 1)
13                elif num_str[i] == d:
14                    dp[i] += dp[i+1]
15        return dp[0] + sum(len(digits) ** i for i in range(1,len(num_str)))

Java Solution

1
2Java
3  class Solution {
4  public int atMostNGivenDigitSet(String[] digits, int n) {
5    String num = Integer.toString(n);
6    int len = num.length();
7    int[] dp = new int[len + 1];
8    dp[len] = 1;
9    int total = 0;
10
11    for (int i = len - 1; i >= 0; i--) {
12      int curr_num = num.charAt(i) - '0';
13      for (String d : digits) {
14        if (Integer.parseInt(d) < curr_num)
15          dp[i] += Math.pow(digits.length, len - i - 1);
16        else if (Integer.parseInt(d) == curr_num)
17          dp[i] += dp[i + 1];
18      }
19      if (i + 1 < len)
20        total += Math.pow(digits.length, len - i - 1);
21    }
22    return dp[0] + total;
23  }
24}

JavaScript Solution

Note: JavaScript does not have a built-in power function like Python and Java, so the Math.pow() function is used.

1
2JavaScript
3 var atMostNGivenDigitSet = function(digits, n) {
4	var numArr = Array.from(String(n), Number);
5	var numDigits = numArr.length;
6	var dp = Array(numDigits + 1).fill(0);
7	dp[numDigits] = 1;
8	var count = 0;
9
10	for (var i = numDigits - 1; i >= 0; i--) {
11		var digit = numArr[i];
12		for (var d of digits) {
13			var num = Number(d);
14			if (num < digit) {
15				dp[i] += Math.pow(digits.length, numDigits - i - 1);
16			} else if (num === digit) {
17				dp[i] += dp[i+1];
18			}
19		}
20		if (i + 1 < numDigits) {
21			count += Math.pow(digits.length, numDigits - i - 1);
22		}
23	}
24	return dp[0] + count;
25};

C++ Solution

1
2C++
3class Solution {
4public:
5    int atMostNGivenDigitSet(vector<string>& digits, int n) {
6        string num = to_string(n);
7        vector<int> dp(num.size() + 1);
8        dp.back() = 1;
9        int count = 0;
10
11        for (int i = num.size() - 1; i >= 0; --i) {
12            for (const string& d : digits) {
13                if (d[0] < num[i]) {
14                    dp[i] += pow(digits.size(), num.size() - i - 1);
15                } else if (d[0] == num[i]) {
16                    dp[i] += dp[i + 1];
17                }
18            }
19            if (i + 1 < num.size()) {
20                count += pow(digits.size(), num.size() - i - 1);
21            }
22        }
23        return dp[0] + count;
24    }
25}; 

C# Solution

1
2C
3public class Solution {
4    public int AtMostNGivenDigitSet(string[] digits, int n) {
5      var num = n.ToString();
6      var dp = new int[num.Length + 1];
7      dp[num.Length] = 1;
8      var count = 0;
9
10      for (int i = num.Length - 1; i >= 0; i--) {
11          foreach (var d in digits) {
12              var digit = d[0] - '0';
13              var currentNum = num[i] - '0';
14              if (digit < currentNum)
15                  dp[i] += (int)Math.Pow(digits.Length, num.Length - i - 1);
16              else if (digit == currentNum)
17                  dp[i] += dp[i + 1];
18          }
19          if (i + 1 < num.Length) 
20              count += (int)Math.Pow(digits.Length, num.Length - i - 1);
21      }
22
23      return dp[0] + count;
24   }
25}

All of these solutions follow the same logic, though each language has its coding constructs and conventions. Each calculates the total in two steps: first by counting numbers that have less digits than N, then by counting numbers that have same number of digits as N but are less than N. These solutions have a time complexity of O(log(N)) because we have to traverse each digit of N just once, where N is the input number. The space complexity is also O(log(N)) because we create a dynamic programming array dp[] of size equal to the number of digits in N.

Note: The C++ solution presented here requires you to include the following libraries:

  • <string>
  • <vector>
  • <cmath>

And the C# solution needs to include the System namespace to use the Math.Pow function.# Test Cases for Validation

In order to validate the correctness of your code, you should implement tests. These tests can be used to verify if the solutions in different programming languages are correct or not.

  1. Test Case 1:

For the input digits = ["3","4","8"] and n = 4, the output will be 2, namely 3 and 4.

  1. Test Case 2:

For the input digits = ["3","7","8","9"] and n = 9363, the output will be 589. It is not practical to list all the numbers, so we are just displaying the count.

  1. Test Case 3:

For the input digits = ["1","2","3","4","5","6","7","8","9"] and n = 1000000000, the output will be 29523.

These test cases should run successfully on your Python, Java, JavaScript, C++, and C# solutions. These verify that the solutions are correctly determining all possible numbers that can be formed from a given subset of digits that are less than or equal to a given number N.

Remember, thorough testing is not just about validating the correct cases—it's also a matter of handling any edge cases that might not have been considered. Ensure also to test your functions with a variety of different numbers to validate that these handle all possible cases.

Feel free to add more test cases if necessary, especially when dealing with special cases such as when the number N contains digits not present in the digit subset or when the digit subset contains only a single digit. You should also take care to test your solutions with a variety of large inputs to verify that they perform efficiently in all situations.


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫