1. 概述
本文将探讨在Java中从给定数字字符串生成所有可能IP地址的不同方法。这是一个常见的面试题,我们将深入分析问题陈述,并介绍多种解决方案,包括暴力迭代、回溯和动态规划。
2. 理解问题
给定一个仅包含数字的字符串S,目标是通过在S中插入点号来生成所有有效的IP地址。不能删除或重排任何数字,结果顺序不限。
例如,对于字符串"101024"
,输出应为:
["1.0.10.24","1.0.102.4","10.1.0.24","10.10.2.4","101.0.2.4"]
有效的IPv4地址由四个用点分隔的八位组组成,每个八位组是0到255之间的数字。有效IPv4地址示例:
0.0.0.0
192.168.0.8
234.223.43.42
255.255.255.0
3. 迭代法
这种方法使用三层嵌套循环遍历可能的分割位置:外层循环处理第一部分,中间循环处理第二部分,内层循环处理第三部分,剩余部分作为第四段。
获得分段组合后,使用isValid()
方法验证每段是否符合IP地址规范。该方法检查数值范围(0-255)并排除前导零:
public List<String> restoreIPAddresses(String s) {
List<String> result = new ArrayList<>();
if (s.length() < 4 || s.length() > 12) {
return result;
}
for (int i = 1; i < 4; i++) {
for (int j = i + 1; j < Math.min(i + 4, s.length()); j++) {
for (int k = j + 1; k < Math.min(j + 4, s.length()); k++) {
String part1 = s.substring(0, i);
String part2 = s.substring(i, j);
String part3 = s.substring(j, k);
String part4 = s.substring(k);
if (isValid(part1) && isValid(part2) && isValid(part3) && isValid(part4)) {
result.add(part1 + "." + part2 + "." + part3 + "." + part4);
}
}
}
}
return result;
}
private boolean isValid(String part) {
if (part.length() > 1 && part.startsWith("0")) { return false; }
int num = Integer.parseInt(part);
return num >= 0 && num <= 255;
}
restoreIPAddresses()
方法生成并返回所有有效IP地址。它首先检查字符串长度是否在4-12字符范围内,否则返回空列表(因为无法形成有效IP)。
使用嵌套循环遍历所有可能的点号位置,导致时间复杂度为O(n²)(不是O(n³),因为外层循环迭代次数固定)。每次迭代创建临时子串,空间复杂度为O(n)。
4. 回溯法
回溯法通过将字符串分成四部分生成所有可能的IP地址。每步使用相同的isValid()
方法验证字符串是否非空且无前导零。若有效则继续处理下一段,当四段完整且用尽所有字符时,将结果加入列表:
public List<String> restoreIPAddresses(String s) {
List<String> result = new ArrayList<>();
backtrack(result, s, new ArrayList<>(), 0);
return result;
}
private void backtrack(List<String> result, String s, List<String> current, int index) {
if (current.size() == 4) {
if (index == s.length()) {
result.add(String.join(".", current));
}
return;
}
for (int len = 1; len <= 3; len++) {
if (index + len > s.length()) { break;
}
String part = s.substring(index, index + len);
if (isValid(part)) {
current.add(part);
backtrack(result, s, current, index + len);
current.remove(current.size() - 1);
}
}
}
backtrack
函数将字符串分成4个整数段,每段最多3位数字,产生3⁴种分割方式。验证每段有效性耗时O(1)(仅需检查长度和范围)。由于3⁴是常数(与输入长度无关),时间复杂度简化为O(1)。
递归深度限制为4(IP地址固定4段),**空间复杂度简化为O(1)**(配置数量和所需空间均为常数)。
5. 动态规划法
动态规划使用递推公式求解问题,类似于分治策略。我们使用dp
数组存储所有有效IP前缀:初始化二维列表dp
,其中dp[i][j]
存储到第i个字符、j个段的所有可能IP地址段。检查所有可能段长(1-3),用isValid()
验证。若有效,将其附加到前一状态的前缀:
public List<String> restoreIPAddresses(String s) {
int n = s.length();
if (n < 4 || n > 12) { return new ArrayList<>();
}
List<String>[][] dp = new ArrayList[n + 1][5];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= 4; j++) {
dp[i][j] = new ArrayList<>();
}
}
dp[0][0].add("");
for (int i = 1; i <= n; i++) {
for (int k = 1; k <= 4; k++) {
for (int j = 1; j <= 3 && i - j >= 0; j++) {
String segment = s.substring(i - j, i);
if (isValid(segment)) {
for (String prefix : dp[i - j][k - 1]) {
dp[i][k].add(prefix.isEmpty() ? segment : prefix + "." + segment);
}
}
}
}
}
return dp[n][4];
}
初始化dp
数组耗时O(n4)→O(n)。填充dp
表的嵌套循环耗时O(n43)→O(n)。空间复杂度O(n4)→O(n)。
6. 结论
本文讨论了从给定数字字符串生成所有可能IP地址组合的方法,并分析了多种解决方案:
✅ 迭代法:实现简单,但时间复杂度较高(O(n²)) ✅ 动态规划法:时间复杂度优化至O(n) ✅ 回溯法:效率最高,时间复杂度达O(1)
回溯法在所有方案中表现最佳,提供了最优的O(1)时间复杂度,是处理此类问题的理想选择。