Skip to content

1012. Numbers With Repeated Digits 👍

  • Time: $O(\log n \cdot 2^{10} \cdot 2 \cdot 10)$
  • Space: $O(\log n \cdot 2^{10} \cdot 2)$
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
 public:
  int numDupDigitsAtMostN(int n) {
    return n - countSpecialNumbers(n);
  }

 private:
  // Same as 2376. Count Special Integers
  int countSpecialNumbers(int n) {
    const int digitSize = log10(n) + 1;
    // dp[i][j][k] := # of special integers with current digit i and `usedMask`
    // j, where k is 0/1 tight constraint
    dp.resize(digitSize + 1, vector<vector<int>>(1 << 10, vector<int>(2, -1)));
    return count(to_string(n), 0, 0, true) - 1;  // - 0;
  }

 private:
  vector<vector<vector<int>>> dp;

  int count(const string& s, int i, int usedMask, bool isTight) {
    if (i == s.length())
      return 1;
    if (dp[i][usedMask][isTight] != -1)
      return dp[i][usedMask][isTight];

    int res = 0;

    const int maxDigit = isTight ? s[i] - '0' : 9;
    for (int d = 0; d <= maxDigit; ++d) {
      // `d` is used.
      if (usedMask >> d & 1)
        continue;
      // Use `d` now.
      const bool nextIsTight = isTight && (d == maxDigit);
      if (usedMask == 0 && d == 0)  // Don't count leading 0s as used.
        res += count(s, i + 1, usedMask, nextIsTight);
      else
        res += count(s, i + 1, usedMask | 1 << d, nextIsTight);
    }

    return dp[i][usedMask][isTight] = res;
  }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
  public int numDupDigitsAtMostN(int n) {
    return n - countSpecialNumbers(n);
  }

  // Same as 2376. Count Special Integers
  public int countSpecialNumbers(int n) {
    final int digitSize = (int) Math.log10(n) + 1;
    // dp[i][j][k] := # of special integers with current d i and `usedMask`
    // j, where k is 0/1 tight constraint
    dp = new Integer[digitSize + 1][1 << 10][2];
    return count(String.valueOf(n), 0, 0, true) - 1; // - 0;
  }

  private Integer[][][] dp;

  private int count(final String s, int i, int usedMask, boolean isTight) {
    if (i == s.length())
      return 1;
    if (dp[i][usedMask][isTight ? 1 : 0] != null)
      return dp[i][usedMask][isTight ? 1 : 0];

    int res = 0;

    final int maxDigit = isTight ? s.charAt(i) - '0' : 9;
    for (int d = 0; d <= maxDigit; ++d) {
      // `d` is used.
      if ((usedMask >> d & 1) == 1)
        continue;
      // Use `d` now.
      final boolean nextIsTight = isTight && (d == maxDigit);
      if (usedMask == 0 && d == 0) // Don't count leading 0s as used.
        res += count(s, i + 1, usedMask, nextIsTight);
      else
        res += count(s, i + 1, usedMask | 1 << d, nextIsTight);
    }

    return dp[i][usedMask][isTight ? 1 : 0] = res;
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution:
  def numDupDigitsAtMostN(self, n: int) -> int:
    return n - self._countSpecialNumbers(n)

  def _countSpecialNumbers(self, n: int) -> int:
    s = str(n)

    # dp(i, j, k) := # of special integers with current digit i and `usedMask`
    # j, where k is 0/1 tight constraint
    @functools.lru_cache(None)
    def dp(i: int, usedMask: int, isTight: bool) -> int:
      if i == len(s):
        return 1

      res = 0

      maxDigit = int(s[i]) if isTight else 9
      for d in range(maxDigit + 1):
        # `d` is used.
        if usedMask >> d & 1:
          continue
        # Use `d` now.
        nextIsTight = isTight and (d == maxDigit)
        if usedMask == 0 and d == 0:  # Don't count leading 0s as used.
          res += dp(i + 1, usedMask, nextIsTight)
        else:
          res += dp(i + 1, usedMask | 1 << d, nextIsTight)

      return res

    return dp(0, 0, True) - 1  # - 0