Medium
题目描述
有效的 IP 地址正好由四个整数组成,整数之间用单点分隔。每个整数都在 0 到 255(包含)之间且不能含有前导零。
例如,“0.1.2.201” 和 “192.168.1.1” 是有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是无效的 IP 地址。
给定一个只包含数字的字符串 s,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入句点来形成。你不能重新排序或删除 s 中的任何数字。你可以按任何顺序返回有效的 IP 地址。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000"
输出:["0.0.0.0"]
示例 3:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
提示:
- 1 <= s.length <= 20
- s 仅由数字组成
解题思路
这是一个典型的回溯算法问题。我们需要在字符串中插入 3 个点,将字符串分割成 4 段,每段都必须是有效的 IP 地址段。
解题思路:
回溯法:使用递归回溯枚举所有可能的分割方案。从字符串开始位置出发,每次尝试取 1、2、3 个字符作为一段。
有效性检查:对于每一段,需要验证:
- 数值在 0-255 范围内
- 不能有前导零(除了单独的"0")
- 长度不超过3
剪枝优化:
- 如果剩余字符数不足以构成剩余段数的最少字符,直接返回
- 如果剩余字符数超过剩余段数的最多字符(每段最多3个),直接返回
终止条件:当分割出 4 段且用完所有字符时,找到一个有效解。
这种方法时间复杂度相对较低,因为有效的 IP 地址数量有限,每段最多 3 位数字,总的搜索空间不大。
代码实现
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> result;
vector<string> path;
backtrack(s, 0, path, result);
return result;
}
private:
void backtrack(const string& s, int start, vector<string>& path, vector<string>& result) {
// 如果已经分割出4段
if (path.size() == 4) {
if (start == s.length()) {
result.push_back(path[0] + "." + path[1] + "." + path[2] + "." + path[3]);
}
return;
}
// 剪枝:剩余字符数不够或太多
int remaining = s.length() - start;
int segments = 4 - path.size();
if (remaining < segments || remaining > segments * 3) {
return;
}
// 尝试取1-3个字符作为一段
for (int len = 1; len <= 3 && start + len <= s.length(); len++) {
string segment = s.substr(start, len);
if (isValid(segment)) {
path.push_back(segment);
backtrack(s, start + len, path, result);
path.pop_back();
}
}
}
bool isValid(const string& segment) {
if (segment.empty() || segment.length() > 3) return false;
if (segment.length() > 1 && segment[0] == '0') return false;
int num = stoi(segment);
return num >= 0 && num <= 255;
}
};
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
result = []
path = []
def backtrack(start):
# 如果已经分割出4段
if len(path) == 4:
if start == len(s):
result.append('.'.join(path))
return
# 剪枝:剩余字符数不够或太多
remaining = len(s) - start
segments = 4 - len(path)
if remaining < segments or remaining > segments * 3:
return
# 尝试取1-3个字符作为一段
for length in range(1, 4):
if start + length > len(s):
break
segment = s[start:start + length]
if self.is_valid(segment):
path.append(segment)
backtrack(start + length)
path.pop()
backtrack(0)
return result
def is_valid(self, segment):
if not segment or len(segment) > 3:
return False
if len(segment) > 1 and segment[0] == '0':
return False
return 0 <= int(segment) <= 255
public class Solution {
public IList<string> RestoreIpAddresses(string s) {
var result = new List<string>();
var path = new List<string>();
Backtrack(s, 0, path, result);
return result;
}
private void Backtrack(string s, int start, List<string> path, List<string> result) {
// 如果已经分割出4段
if (path.Count == 4) {
if (start == s.Length) {
result.Add(string.Join(".", path));
}
return;
}
// 剪枝:剩余字符数不够或太多
int remaining = s.Length - start;
int segments = 4 - path.Count;
if (remaining < segments || remaining > segments * 3) {
return;
}
// 尝试取1-3个字符作为一段
for (int len = 1; len <= 3 && start + len <= s.Length; len++) {
string segment = s.Substring(start, len);
if (IsValid(segment)) {
path.Add(segment);
Backtrack(s, start + len, path, result);
path.RemoveAt(path.Count - 1);
}
}
}
private bool IsValid(string segment) {
if (string.IsNullOrEmpty(segment) || segment.Length > 3) return false;
if (segment.Length > 1 && segment[0] == '0') return false;
int num = int.Parse(segment);
return num >= 0 && num <= 255;
}
}
var restoreIpAddresses = function(s) {
const result = [];
function isValid(str) {
if (str.length === 0 || str.length > 3) return false;
if (str.length > 1 && str[0] === '0') return false;
const num = parseInt(str);
return num >= 0 && num <= 255;
}
function backtrack(start, parts) {
if (parts.length === 4) {
if (start === s.length) {
result.push(parts.join('.'));
}
return;
}
for (let len = 1; len <= 3 && start + len <= s.length; len++) {
const part = s.substring(start, start + len);
if (isValid(part)) {
parts.push(part);
backtrack(start + len, parts);
parts.pop();
}
}
}
backtrack(0, []);
return result;
};
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(3^4) = O(81) |
| 空间复杂度 | O(4) = O(1) |
说明:
- 时间复杂度:每段最多尝试3种长度,共4段,所以最多 3^4 = 81 种组合
- 空间复杂度:递归深度最多为4,path数组最多存储4个字符串
相关题目
- . IP to CIDR (Medium)