> ## Documentation Index
> Fetch the complete documentation index at: https://leetcode-py.wisl.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> leetcode-py is a Python LeetCode practice environment generator with one CLI: lcpy. It is not a service or platform.
> Each problem is a directory under leetcode/ with README.md, solution.py, test_solution.py, helpers.py, and playground.ipynb. lcpy gen creates them from JSON templates bundled with the package.
> Examples are backed by tests; copy them verbatim.

# Decode Ways II Python Solution with Tests

> Tested Python solution for LeetCode 639 with 30 pytest cases. Generate a practice environment with lcpy.

LeetCode 639, [Hard](/catalog/hard). Topics: [String](/catalog/topics/string), [Dynamic Programming](/catalog/topics/dynamic-programming). [View on LeetCode](https://leetcode.com/problems/decode-ways-ii/description/).

Generate this problem as a practice environment: tested reference solution, 30 [parametrized pytest cases](/practice/testing), and a playground notebook:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
lcpy gen -n 639   # by problem number
lcpy gen -s decode_ways_ii   # by problem name
```

## Problem

A message containing letters from A-Z can be encoded into numbers using the following mapping:

```
'A' -> "1"
'B' -> "2"
...
'Z' -> "26"
```

To decode an encoded message, all the digits must be grouped then mapped back into letters using the reverse of the mapping above (there may be multiple ways). For example, "11106" can be mapped into:

* "AAJF" with the grouping (1 1 10 6)
* "KJF" with the grouping (11 10 6)

Note that the grouping (1 11 06) is invalid because "06" cannot be mapped into 'F' since "6" is different from "06".

In addition to the mapping above, an encoded message may contain the '*' character, which can represent any digit from '1' to '9' ('0' is excluded). For example, the encoded message "1*" may represent any of the encoded messages "11", "12", "13", "14", "15", "16", "17", "18", or "19". Decoding "1\*" is equivalent to decoding any of the encoded messages it can represent.

Given a string s consisting of digits and '\*' characters, return the number of ways to decode it.

Since the answer may be very large, return it modulo 10^9 + 7.

### Examples

```
Input: s = "*"
Output: 9
Explanation: The encoded message can represent any of the encoded messages "1" through "9". Each of these can be decoded to the strings "A" through "I" respectively. Hence, there are a total of 9 ways to decode "*".
```

```
Input: s = "1*"
Output: 18
Explanation: The encoded message can represent any of the encoded messages "11" through "19". Each of these encoded messages have 2 ways to be decoded (e.g. "11" can be decoded to "AA" or "K"). Hence, there are a total of 9 * 2 = 18 ways to decode "1*".
```

```
Input: s = "2*"
Output: 15
Explanation: The encoded message can represent any of the encoded messages "21" through "29". "21" through "26" have 2 ways of being decoded, but "27" through "29" only have 1 way. Hence, there are a total of (6 * 2) + (3 * 1) = 15 ways to decode "2*".
```

### Constraints

* 1 \<= s.length \<= 10^5
* s\[i] is a digit or '\*'.

## Solution

Reference implementation from [solution.py on GitHub](https://github.com/wislertt/leetcode-py/blob/main/leetcode/decode_ways_ii/solution.py), full suite in [test\_solution.py](https://github.com/wislertt/leetcode-py/blob/main/leetcode/decode_ways_ii/test_solution.py):

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
class Solution:
    # Time: O(n)
    # Space: O(1)
    def num_decodings(self, s: str) -> int:
        mod = 1_000_000_007
        prev = 1
        curr = self._ways_single(s[0])
        for i in range(1, len(s)):
            pair = self._ways_pair(s[i - 1], s[i])
            prev, curr = curr, (curr * self._ways_single(s[i]) + prev * pair) % mod
        return curr

    def _ways_single(self, ch: str) -> int:
        if ch == "*":
            return 9
        return 0 if ch == "0" else 1

    def _ways_pair(self, a: str, b: str) -> int:
        if a == "*":
            if b == "*":
                return 15
            return 2 if b <= "6" else 1
        if b == "*":
            return 9 if a == "1" else (6 if a == "2" else 0)
        if a == "0":
            return 0
        return 1 if int(a + b) <= 26 else 0
```

## Complexity

| Time | Space |
| - | - |
| O(n) | O(1) |

## Tags


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.