Approach (short): Use digit-DP over decimal digits of N with state (pos, tight, prev_digit, rem). pos = index from 0 (most-significant) to L, tight indicates prefix equals N's prefix, prev_digit in {-1,0..9} (-1 means no previous non-discarded digit / leading zeros), rem is current remainder modulo k. Count numbers in [0..N] where no two adjacent equal digits (adjacency ignores leading zeros as "no previous digit").Recursive implementation with memoization (iterative possible; recursion shown for clarity):python
from functools import lru_cache
def count_no_adj_eq_divisible(N: int, k: int) -> int:
s = list(map(int, str(N)))
L = len(s)
@lru_cache(maxsize=None)
def dp(pos: int, tight: int, prev: int, rem: int) -> int:
# pos = index in s (0..L). At pos==L we processed all digits.
if pos == L:
return 1 if rem == 0 else 0
limit = s[pos] if tight else 9
total = 0
for d in range(0, limit + 1):
ntight = tight and (d == limit)
# leading zeros: if prev == -1 and d == 0, we keep prev = -1 (no adjacency constraint)
if prev == -1 and d == 0:
nprev = -1
nrem = (rem * 10 + d) % k # including zeros still affects remainder for counting 0..N
total += dp(pos + 1, ntight, nprev, nrem)
else:
# normal digit (either prev != -1 or d != 0)
if prev != -1 and d == prev:
continue # adjacent equal -> invalid
nprev = d
nrem = (rem * 10 + d) % k
total += dp(pos + 1, ntight, nprev, nrem)
return total
# Start with pos=0, tight=1, prev=-1, rem=0
return dp(0, 1, -1, 0)
Key concepts and reasoning:- prev = -1 marks "no meaningful previous digit" so leading zeros don't trigger adjacency checks.- We still incorporate leading zeros into remainder so numbers like 0 are handled; if you want to exclude 0 explicitly, subtract if needed.- tight ensures we don't exceed N.- Memoization key is (pos, tight, prev, rem). Because tight is boolean, prev ∈ { -1,0..9 } (11 values), pos ≤ 19 for 10^18, rem < k (≤100). So worst-case states ≈ 20*2*11*k ≈ 44k for k=100 — tiny and safe for production.Memory complexity and optimizations:- Space: ~O(L * 2 * 11 * k) states; each cached value is an integer — with k≤100 this is small (~44k entries). For safety in SRE tooling, use iterative DP (bottom-up) to avoid recursion stack and to tightly control memory layout.- Use compact arrays: pack prev+1 (0..10) and tight (0/1) into indices and store dp[pos][packed] as arrays of length k with int64 values. This yields contiguous memory, faster lookups.- If doing many queries with varying N but same k, precompute transitions for digits and reuse DP skeleton; only fill per N.- To reduce GC pressure in Python, use lru_cache with maxsize or switch to array-backed implementation in C-extension or use PyPy/CPython with minimal object churn.- For extreme performance (SRE alerting/monitoring), implement in Go/Java with iterative DP and preallocated int64[][][] arrays; avoid recursion, avoid hash-based memoization.Edge cases:- N=0: function returns 1 if 0 % k == 0 and no adjacency violation (handled).- Leading zeros: treated so numbers shorter than L are considered.- k=1 trivial: all numbers satisfying adjacency constraint count; can short-circuit.Time complexity:- O(L * 10 * states_per_pos) ≈ O(L * 10 * 2 * 11 * k) ~ O(L * k) effectively with small constant (L≤19, k≤100) — fast.Production notes for SRE:- Prefer iterative DP with arrays for predictable memory and latency.- Add input validation, timeouts, and caching for repeated k queries.- Benchmark with worst-case N and k to pick language/representation that fits latency SLO.