在Java中根据给定的数字字符串生成所有可能的IP地址

2025/04/19

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"]

上面的输出列出了给定字符串可以构成的所有有效IP地址,有效的IPv4地址由4个八位字节组成,每个八位字节之间用点分隔。每个八位字节都是一个介于0到255之间的数字,这意味着有效的IPv4地址如下所示

0.0.0.0
192.168.0.8
234.223.43.42
255.255.255.0

3. 迭代方法

在这个方法中,我们将使用3个嵌套循环来遍历将字符串拆分成4个部分的可能位置。外层循环遍历第一部分,中间层循环遍历第二部分,内层循环遍历第三部分,我们将字符串的剩余部分视为第四个部分。

一旦我们获得了段组合,我们将提取子字符串并使用isValid()方法验证每个段是否构成IP地址的有效部分。isValid()方法负责检查给定字符串是否是IP地址的有效部分,它确保整数值介于0到255之间,并且没有前导0:

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地址。

我们使用嵌套循环来迭代这3个点的所有可能位置,这会使时间复杂度增加到O(n²)。它不是O(n³),因为外层循环的迭代次数是固定的。此外,我们在每次迭代中都会创建临时子字符串,这会使整体空间复杂度增加到O(n)

4. 回溯法

回溯时,我们将字符串拆分成四部分,生成所有可能的IP地址。每一步,我们都使用相同的isValid()方法执行验证,检查字符串是否非空且不包含前导0。如果是有效字符串,则进入下一部分。完成所有四部分并使用所有字符后,我们会将它们添加到结果列表中:

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位数字。这样一来,就有34种可能的拆分方式。评估每个段是否有效需要O(1)的时间复杂度,因为它需要检查段的长度和范围。由于可能的配置数量34是常数,并且与输入字符串的长度无关,因此时间复杂度简化为O(1)

由于IP地址恰好有4个段,因此递归深度限制为4。由于配置数量和所需空间为常数,因此空间复杂度简化为O(1)

5. 动态规划方法

动态规划是一种流行的算法范式,它使用递归公式来求解。它类似于分治策略,因为它将问题分解为更小的子问题,其解决方案使用数组来存储先前计算值的结果。

现在,我们将使用dp数组来存储所有有效的IP前缀。首先初始化一个二维列表dp,其中dp[i][j]存储所有可能的IP地址段,直到第i个字符,共j个段。之后,我们检查所有可能的段长度(1到3),并使用isValid()方法验证每个段。如果某个段有效,则将其附加到前一个状态的前缀中,我们将有效IP地址的最终结果存储并返回到dp[n][4]中。

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(n*4)的时间复杂度,简化为O(n)。负责填充dp表的嵌套循环需要O(n*4*3) 的时间复杂度,简化为O(n)。空间复杂度也是O(n*4),简化为O(n)

6. 总结

在本文中,我们讨论了如何根据给定的数字字符串生成所有可能的IP地址组合,并了解了各种解决方法。迭代方法简单易懂,但时间复杂度较高,为O(n²)。为了获得更好的性能,我们可以使用动态规划方法,其时间复杂度仅为O(n)。

回溯编程方法被证明是最有效的方法,因为在我们讨论的所有其他方法中,它具有最佳的运行时间O(1)

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章