A detailed guide to solving Restore IP Addresses with backtracking over four valid IP segments.
Problem Restatement
We are given a string s containing only digits.
We need to return all possible valid IP addresses that can be formed by inserting dots into s.
We cannot reorder digits.
We cannot remove digits.
We only insert exactly three dots so that the string becomes four valid IP address segments.
A valid IP address has exactly four integers separated by dots. Each integer must be between 0 and 255, inclusive, and it cannot contain leading zeroes. The valid addresses may be returned in any order. The official constraints are 1 <= s.length <= 20, and s contains only digits.
Input and Output
| Item | Meaning |
|---|---|
| Input | A digit string s |
| Output | All valid IP addresses formed from s |
| Segment count | Exactly 4 |
| Segment length | 1 to 3 digits |
| Segment value | 0 to 255 |
| Leading zero rule | "0" is valid, but "00" and "01" are invalid |
Function shape:
class Solution:
def restoreIpAddresses(self, s: str) -> list[str]:
...Examples
Example 1:
s = "25525511135"Valid answers:
[
"255.255.11.135",
"255.255.111.35",
]Example 2:
s = "0000"Each segment must be "0".
Answer:
["0.0.0.0"]Example 3:
s = "101023"Valid answers:
[
"1.0.10.23",
"1.0.102.3",
"10.1.0.23",
"10.10.2.3",
"101.0.2.3",
]First Thought: Place Three Dots
An IP address has four parts.
So we can think of the problem as choosing four substrings from s.
For each segment, we can choose length:
1, 2, or 3because the largest valid segment is 255, which has three digits.
After choosing four segments, the whole string must be used.
This is a small search tree, so backtracking fits naturally.
Key Insight
At each step, choose the next IP segment.
The recursive state needs:
| State | Meaning |
|---|---|
start | Current index in s |
path | Segments chosen so far |
ans | Valid IP addresses found |
At each call, try the next segment length:
1
2
3Then validate the segment.
A segment is valid if:
- It has no leading zero unless it is exactly
"0". - Its integer value is at most
255.
Segment Validation
A helper function makes the logic clear.
def valid(segment: str) -> bool:
if len(segment) > 1 and segment[0] == "0":
return False
return int(segment) <= 255Examples:
| Segment | Valid? | Reason |
|---|---|---|
"0" | Yes | Single zero is allowed |
"00" | No | Leading zero |
"01" | No | Leading zero |
"10" | Yes | Between 0 and 255 |
"255" | Yes | Maximum valid value |
"256" | No | Greater than 255 |
Algorithm
Create:
ans = []
path = []Define:
backtrack(start)At each call:
- If
len(path) == 4, check whetherstart == len(s). - If both are true, join the segments with dots and add to
ans. - If four segments are already chosen but characters remain, stop.
- Otherwise, try segment lengths
1,2, and3. - If the segment is valid, append it to
path. - Recurse from the next index.
- Pop the segment to try another choice.
Pruning
We can stop early when the remaining string length cannot fit the remaining segments.
Let:
remaining_chars = len(s) - start
remaining_parts = 4 - len(path)Each remaining segment needs at least one digit and at most three digits.
So the path is impossible if:
remaining_chars < remaining_partsor:
remaining_chars > remaining_parts * 3This pruning is not required for correctness, but it avoids useless calls.
Walkthrough
Use:
s = "101023"Start with:
path = []
start = 0Try first segment "1":
path = ["1"]
start = 1Now try "0":
path = ["1", "0"]
start = 2Now try "1":
path = ["1", "0", "1"]
start = 3The last segment would be "023", which has a leading zero, so this route fails.
Backtrack and try "10":
path = ["1", "0", "10"]
start = 4The final segment is "23".
So we get:
"1.0.10.23"Backtracking continues and finds:
"1.0.102.3"
"10.1.0.23"
"10.10.2.3"
"101.0.2.3"Correctness
The algorithm builds IP addresses by choosing four segments in order from the original string.
Every chosen segment is a contiguous substring of s, and the recursive call always moves start forward. Therefore, the algorithm never reorders or removes digits.
The algorithm adds an answer only when exactly four segments have been chosen and all characters have been consumed. Therefore, every returned string uses all digits and has exactly four segments.
Each segment is checked before it is added to path. The validation rejects leading zeroes and values above 255. Therefore, every returned address is valid.
Now consider any valid IP address that can be formed from s. Its four segments have lengths between 1 and 3. The backtracking loop tries all segment lengths 1, 2, and 3 at each step. Since all four segments are valid, the algorithm will accept each of them and follow the exact path that forms this address. So every valid address is generated.
Therefore, the algorithm returns exactly all valid IP addresses.
Complexity
There are only four segments, and each segment has at most three length choices.
So the search space is bounded by:
3^4 = 81The input length can be up to 20, but no valid IP address can use more than 12 digits.
| Metric | Value | Why |
|---|---|---|
| Time | O(1) relative to input length | At most 81 segment choices are explored |
| Space | O(1) extra | The recursion depth is at most 4 |
| Output space | O(k) | k valid addresses are returned |
It is also acceptable to describe the time as bounded by a small constant because IPv4 always has exactly four segments.
Implementation
class Solution:
def restoreIpAddresses(self, s: str) -> list[str]:
ans = []
path = []
def valid(segment: str) -> bool:
if len(segment) > 1 and segment[0] == "0":
return False
return int(segment) <= 255
def backtrack(start: int) -> None:
remaining_chars = len(s) - start
remaining_parts = 4 - len(path)
if remaining_chars < remaining_parts:
return
if remaining_chars > remaining_parts * 3:
return
if len(path) == 4:
if start == len(s):
ans.append(".".join(path))
return
for length in range(1, 4):
end = start + length
if end > len(s):
break
segment = s[start:end]
if not valid(segment):
continue
path.append(segment)
backtrack(end)
path.pop()
backtrack(0)
return ansCode Explanation
The answer list stores completed IP addresses:
ans = []The current partial IP address is:
path = []The helper checks one segment:
def valid(segment: str) -> bool:Leading zeroes are rejected:
if len(segment) > 1 and segment[0] == "0":
return FalseThen the numeric range is checked:
return int(segment) <= 255The recursive function starts at an index in s:
def backtrack(start: int) -> None:Pruning checks whether the remaining characters can fit the remaining segments:
remaining_chars = len(s) - start
remaining_parts = 4 - len(path)Then:
if remaining_chars < remaining_parts:
return
if remaining_chars > remaining_parts * 3:
returnIf four segments have been chosen, we only accept the path when all characters are used:
if len(path) == 4:
if start == len(s):
ans.append(".".join(path))
returnThen we try segment lengths 1, 2, and 3:
for length in range(1, 4):Extract the candidate segment:
segment = s[start:end]If valid, choose it and recurse:
path.append(segment)
backtrack(end)
path.pop()The pop() undoes the choice so the next candidate can be tried.
Testing
Because output order may vary, compare sorted lists.
def check(s, expected):
sol = Solution()
result = sol.restoreIpAddresses(s)
assert sorted(result) == sorted(expected)
def run_tests():
check("25525511135", [
"255.255.11.135",
"255.255.111.35",
])
check("0000", [
"0.0.0.0",
])
check("101023", [
"1.0.10.23",
"1.0.102.3",
"10.1.0.23",
"10.10.2.3",
"101.0.2.3",
])
check("1111", [
"1.1.1.1",
])
check("010010", [
"0.10.0.10",
"0.100.1.0",
])
check("99999999999999999999", [])
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
"25525511135" | Main example |
"0000" | Zero segments are valid only as "0" |
"101023" | Multiple valid segment lengths |
"1111" | Exactly one digit per segment |
"010010" | Leading-zero handling |
| Very long string | Cannot fit into four IP segments |