Missing undo step on backtrack
Wrong move: Mutable state leaks between branches.
Usually fails on: Later branches inherit selections from earlier branches.
Fix: Always revert state changes immediately after recursive call.
Move from brute-force thinking to an efficient approach using backtracking strategy.
A valid IP address consists of exactly four integers separated by single dots. Each integer is between 0 and 255 (inclusive) and cannot have leading zeros.
"0.1.2.201" and "192.168.1.1" are valid IP addresses, but "0.011.255.245", "192.168.1.312" and "192.168@1.1" are invalid IP addresses.Given a string s containing only digits, return all possible valid IP addresses that can be formed by inserting dots into s. You are not allowed to reorder or remove any digits in s. You may return the valid IP addresses in any order.
Example 1:
Input: s = "25525511135" Output: ["255.255.11.135","255.255.111.35"]
Example 2:
Input: s = "0000" Output: ["0.0.0.0"]
Example 3:
Input: s = "101023" Output: ["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
Constraints:
1 <= s.length <= 20s consists of digits only.Problem summary: A valid IP address consists of exactly four integers separated by single dots. Each integer is between 0 and 255 (inclusive) and cannot have leading zeros. For example, "0.1.2.201" and "192.168.1.1" are valid IP addresses, but "0.011.255.245", "192.168.1.312" and "192.168@1.1" are invalid IP addresses. Given a string s containing only digits, return all possible valid IP addresses that can be formed by inserting dots into s. You are not allowed to reorder or remove any digits in s. You may return the valid IP addresses in any order.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Backtracking
"25525511135"
"0000"
"101023"
ip-to-cidr)import java.util.*;
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> ans = new ArrayList<>();
backtrack(s, 0, 0, new ArrayList<>(), ans);
return ans;
}
private void backtrack(String s, int idx, int parts, List<String> cur, List<String> ans) {
if (parts == 4) {
if (idx == s.length()) ans.add(String.join(".", cur));
return;
}
for (int len = 1; len <= 3 && idx + len <= s.length(); len++) {
String seg = s.substring(idx, idx + len);
if (seg.length() > 1 && seg.charAt(0) == '0') break;
int val = Integer.parseInt(seg);
if (val > 255) continue;
cur.add(seg);
backtrack(s, idx + len, parts + 1, cur, ans);
cur.remove(cur.size() - 1);
}
}
}
import "strconv"
func restoreIpAddresses(s string) []string {
ans := []string{}
cur := []string{}
var backtrack func(idx, parts int)
backtrack = func(idx, parts int) {
if parts == 4 {
if idx == len(s) {
ans = append(ans, cur[0]+"."+cur[1]+"."+cur[2]+"."+cur[3])
}
return
}
for l := 1; l <= 3 && idx+l <= len(s); l++ {
seg := s[idx : idx+l]
if len(seg) > 1 && seg[0] == '0' {
break
}
v, _ := strconv.Atoi(seg)
if v > 255 {
continue
}
cur = append(cur, seg)
backtrack(idx+l, parts+1)
cur = cur[:len(cur)-1]
}
}
backtrack(0, 0)
return ans
}
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
ans: List[str] = []
cur: List[str] = []
def backtrack(idx: int, parts: int) -> None:
if parts == 4:
if idx == len(s):
ans.append('.'.join(cur))
return
for l in range(1, 4):
if idx + l > len(s):
break
seg = s[idx:idx + l]
if len(seg) > 1 and seg[0] == '0':
break
if int(seg) > 255:
continue
cur.append(seg)
backtrack(idx + l, parts + 1)
cur.pop()
backtrack(0, 0)
return ans
impl Solution {
pub fn restore_ip_addresses(s: String) -> Vec<String> {
let mut ans = Vec::new();
let mut cur: Vec<String> = Vec::new();
fn backtrack(s: &str, idx: usize, parts: usize, cur: &mut Vec<String>, ans: &mut Vec<String>) {
if parts == 4 {
if idx == s.len() {
ans.push(cur.join("."));
}
return;
}
for l in 1..=3 {
if idx + l > s.len() {
break;
}
let seg = &s[idx..idx + l];
if seg.len() > 1 && seg.starts_with('0') {
break;
}
if seg.parse::<i32>().unwrap() > 255 {
continue;
}
cur.push(seg.to_string());
backtrack(s, idx + l, parts + 1, cur, ans);
cur.pop();
}
}
backtrack(&s, 0, 0, &mut cur, &mut ans);
ans
}
}
function restoreIpAddresses(s: string): string[] {
const ans: string[] = [];
const cur: string[] = [];
const backtrack = (idx: number, parts: number): void => {
if (parts === 4) {
if (idx === s.length) ans.push(cur.join('.'));
return;
}
for (let len = 1; len <= 3 && idx + len <= s.length; len++) {
const seg = s.slice(idx, idx + len);
if (seg.length > 1 && seg[0] === '0') break;
if (Number(seg) > 255) continue;
cur.push(seg);
backtrack(idx + len, parts + 1);
cur.pop();
}
};
backtrack(0, 0);
return ans;
}
Use this to step through a reusable interview workflow for this problem.
Generate every possible combination without any filtering. At each of n positions we choose from up to n options, giving nⁿ total candidates. Each candidate takes O(n) to validate. No pruning means we waste time on clearly invalid partial solutions.
Backtracking explores a decision tree, but prunes branches that violate constraints early. Worst case is still factorial or exponential, but pruning dramatically reduces the constant factor in practice. Space is the recursion depth (usually O(n) for n-level decisions).
Review these before coding to avoid predictable interview regressions.
Wrong move: Mutable state leaks between branches.
Usually fails on: Later branches inherit selections from earlier branches.
Fix: Always revert state changes immediately after recursive call.