1
dp[0]
Count how many ways a digit string can be decoded back into letters (A=1 … Z=26) using a compact 1-D DP table — each position either extends a 1-digit mapping, a 2-digit mapping, or both, with leading-zero traps at every step.
A phone keypad maps letters to digits: A=1, B=2, …, Z=26. Given a non-empty string of digits, count the number of ways to decode it back into letters.
Example: s = "226". Possible decodings:
"2" "2" "6" → BBF"22" "6" → VF"2" "26" → BZAnswer: 3. Edge case: s = "06" → 0 (leading zero, impossible).
i, look back at the last one character (1-digit code) and the last two characters (2-digit code). If either window is a valid letter code, add the number of ways from before that window. dp[i] = (valid1 ? dp[i-1] : 0) + (valid2 ? dp[i-2] : 0). The recurrence is local: you only ever need the previous two values.dp[0] = 1(empty string decodes one way — it's the "nothing consumed yet" seed). s[i-1] ≠ '0' (i.e. '1'–'9'), add dp[i-1] — every decoding of the prefix can be extended by this single letter.i ≥ 2 and 10 ≤ s[i-2..i) ≤ 26, add dp[i-2] — every decoding ending two positions back can be extended by this pair as one letter.i — a dead end (usually caused by a stray '0').Space reduction: because dp[i] only depends on dp[i-1] and dp[i-2], you can replace the array with two rolling variables (prev2, prev1) for O(1) space — identical to the Fibonacci rolling trick.
'*' digit that counts for 1–9, requiring careful multiplication of branch counts.226. We fill dp[0..3] where dp[i] = ways to decode s[0..i). Base: dp[0] = 1 (empty string decodes one way).function numDecodings(s: string): number {
const n = s.length;
// dp[i] = number of ways to decode s[0..i)
const dp: number[] = new Array(n + 1).fill(0);
dp[0] = 1; // base: empty string has exactly one decoding
for (let i = 1; i <= n; i++) {
// 1-digit: s[i-1] maps to a letter iff it is '1'–'9'
if (s[i - 1] !== '0') {
dp[i] += dp[i - 1];
}
// 2-digit: s[i-2..i) maps to a letter iff the value is 10–26
if (i >= 2) {
const two = parseInt(s.slice(i - 2, i), 10);
if (two >= 10 && two <= 26) {
dp[i] += dp[i - 2];
}
}
}
return dp[n];
}dp is length n+1 (1-indexed so dp[i] covers the first i characters). dp[0] = 1is the "empty prefix" seed — without it, no valid decoding can ever accumulate.s[i-1] is the character at the current position (0-indexed). Any digit except '0' is a valid single-letter code. If valid, propagate dp[i-1] forward.i ≥ 2. parseInt(s.slice(i-2, i), 10) reads the pair as a number. The bounds 10–26capture exactly the letters J–Z; "07" parses to 7 (below 10) and "27" is above 26, so both are rejected. If valid, add dp[i-2].dp[n] counts ways to decode the entire string. A value of 0 means no valid decoding exists (e.g. a bare '0' not covered by any 2-digit window).n positions, O(1) work each. O(n) space for the dp array; this collapses to O(1) with two rolling variables since only the previous two values are ever read.prev2 and prev1 (Fibonacci rolling pattern). Update them at each step: prev2 = prev1; prev1 = cur.'*' stands for any digit 1–9 (nine choices). For the 1-digit branch multiply by 9; for the 2-digit branch carefully count how many pairs 10–26 it can form with the adjacent digit."0" → 0 (no valid code). "10" → 1 (only "10"=J). "30"→ 0 ("30" > 26, and "0" alone is invalid). "1" → 1.dp array: at each position, if dp[i] has a 1-digit contribution, step back by 1; if a 2-digit contribution exists, step back by 2 (prefer one arbitrarily or enumerate all).| "how many ways to decode / interpret a digit string" | 1-D DP: dp[i] += dp[i-1] (1-digit) + dp[i-2] (2-digit) |
| each position depends on the previous 1–2 positions only | rolling-variable O(1) space (Fibonacci pattern) |
| "0" in the input / leading zero in a window | guard: skip 1-digit if s[i-1]==="0"; skip 2-digit if value < 10 or > 26 |
| "count ways" + strings + bounded alphabet | bottom-up DP with small alphabet or digit constraints |
function numDecodings(s: string): number {
const n = s.length;
const dp = new Array(n + 1).fill(0);
dp[0] = 1;
for (let i = 1; i <= n; i++) {
if (s[i - 1] !== '0') dp[i] += dp[i - 1]; // 1-digit
if (i >= 2) {
const two = parseInt(s.slice(i - 2, i), 10);
if (two >= 10 && two <= 26) dp[i] += dp[i - 2]; // 2-digit
}
}
return dp[n];
}s[0..i) (the first i characters). dp[0] = 1 is the empty-prefix seed.numDecodings("226")?numDecodings("06")?i ≥ 2?