LeetCode第93题:复原IP地址

LeetCode第93题:复原IP地址

题目描述

有效的 IP 地址由四个整数组成,整数之间用 . 分隔,其中每个整数位于 0 到 255 之间(包含 0 和 255),且不能含有前导零。

例如:"0.1.2.201""192.168.1.1" 是有效的 IP 地址,但是 "0.011.255.245""192.168.1.312""[email protected]" 是无效的 IP 地址。

给定一个只包含数字的字符串 s,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 . 来形成。你不能重新排序或删除 s 中的任何数字。你可以按任何顺序返回答案。

难度

中等

问题链接

复原IP地址

示例

示例 1:

ini 复制代码
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]

示例 2:

ini 复制代码
输入:s = "0000"
输出:["0.0.0.0"]

示例 3:

arduino 复制代码
输入: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 仅由数字组成

解题思路

这道题要求我们从一个只包含数字的字符串中恢复所有可能的有效 IP 地址。我们可以使用回溯法(深度优先搜索)来解决这个问题。

方法一:回溯法

回溯法是一种通过探索所有可能的候选解来找出所有解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯法会通过在上一步进行一些变化来舍弃该解,即回溯并且再次尝试。

对于这个问题,我们需要在字符串中插入三个点,将其分成四个部分,每个部分都必须是有效的 IP 地址段。我们可以使用回溯法来尝试所有可能的分割方式。

关键点

  1. IP 地址由四个整数组成,每个整数在 0 到 255 之间。
  2. 整数之间用点分隔。
  3. 整数不能有前导零,除非这个整数就是 0。
  4. 我们需要在字符串中插入三个点,将其分成四个部分。

算法步骤分析

回溯法算法步骤

步骤 操作 说明
1 初始化 创建一个空列表来存储结果,初始化回溯函数的参数
2 定义回溯函数 函数接收当前处理的索引、已分割的段数和当前构建的 IP 地址
3 终止条件 如果已经分割了四段且处理完所有字符,将当前 IP 地址添加到结果列表中
4 剪枝 如果段数超过 4 或者处理完所有字符但段数不足 4,直接返回
5 遍历可能的分割点 从当前索引开始,尝试分割出一个有效的 IP 地址段
6 验证 IP 段 检查分割出的段是否是有效的 IP 地址段(0-255,无前导零)
7 递归 如果当前段有效,递归处理剩余部分
8 回溯 回溯并尝试其他可能的分割方式
9 返回结果 返回所有可能的有效 IP 地址

算法可视化

以示例 s = "25525511135" 为例,我们使用回溯法来找出所有可能的有效 IP 地址:

  1. 初始状态:s = "25525511135",我们需要在其中插入三个点。

  2. 第一次分割:

    • 尝试 "2":有效的 IP 段,继续处理 "5525511135"
    • 尝试 "25":有效的 IP 段,继续处理 "525511135"
    • 尝试 "255":有效的 IP 段,继续处理 "25511135"
  3. 对于 "2",第二次分割:

    • 尝试 "2.5":有效的 IP 段,继续处理 "525511135"
    • 尝试 "2.55":有效的 IP 段,继续处理 "25511135"
    • 尝试 "2.552":无效的 IP 段(大于 255),回溯
  4. 对于 "2.5",第三次分割:

    • 尝试 "2.5.5":有效的 IP 段,继续处理 "25511135"
    • 尝试 "2.5.52":有效的 IP 段,继续处理 "5511135"
    • 尝试 "2.5.525":无效的 IP 段(大于 255),回溯
  5. 对于 "2.5.5",第四次分割:

    • 尝试 "2.5.5.25511135":无效的 IP 段(大于 255),回溯
  6. 对于 "2.5.52",第四次分割:

    • 尝试 "2.5.52.5511135":无效的 IP 段(大于 255),回溯
  7. 继续回溯并尝试其他分割方式...

  8. 最终找到两个有效的 IP 地址:

    • "255.255.11.135"
    • "255.255.111.35"

代码实现

C# 实现

csharp 复制代码
public class Solution {
    public IList<string> RestoreIpAddresses(string s) {
        List<string> result = new List<string>();
        Backtrack(s, 0, 0, "", result);
        return result;
    }
    
    private void Backtrack(string s, int index, int segments, string current, List<string> result) {
        // 如果已经处理了4段且用完了所有字符,则找到一个有效的IP地址
        if (segments == 4 && index == s.Length) {
            // 移除最后一个点
            result.Add(current.Substring(0, current.Length - 1));
            return;
        }
        
        // 如果段数超过4或者已经处理完所有字符但段数不足4,则无效
        if (segments > 4 || index == s.Length) {
            return;
        }
        
        // 尝试分割出一个IP段(最多3位数字)
        for (int len = 1; len <= 3 && index + len <= s.Length; len++) {
            string segment = s.Substring(index, len);
            
            // 检查IP段是否有效
            if (IsValidSegment(segment)) {
                // 递归处理剩余部分
                Backtrack(s, index + len, segments + 1, current + segment + ".", result);
            }
        }
    }
    
    private bool IsValidSegment(string segment) {
        // IP段不能有前导零,除非这个段就是0
        if (segment.Length > 1 && segment[0] == '0') {
            return false;
        }
        
        // IP段必须在0到255之间
        int value = int.Parse(segment);
        return value >= 0 && value <= 255;
    }
}

Python 实现

python 复制代码
class Solution:
    def restoreIpAddresses(self, s: str) -> List[str]:
        result = []
        self.backtrack(s, 0, 0, "", result)
        return result
    
    def backtrack(self, s, index, segments, current, result):
        # 如果已经处理了4段且用完了所有字符,则找到一个有效的IP地址
        if segments == 4 and index == len(s):
            # 移除最后一个点
            result.append(current[:-1])
            return
        
        # 如果段数超过4或者已经处理完所有字符但段数不足4,则无效
        if segments > 4 or index == len(s):
            return
        
        # 尝试分割出一个IP段(最多3位数字)
        for length in range(1, 4):
            if index + length > len(s):
                break
                
            segment = s[index:index + length]
            
            # 检查IP段是否有效
            if self.is_valid_segment(segment):
                # 递归处理剩余部分
                self.backtrack(s, index + length, segments + 1, current + segment + ".", result)
    
    def is_valid_segment(self, segment):
        # IP段不能有前导零,除非这个段就是0
        if len(segment) > 1 and segment[0] == '0':
            return False
        
        # IP段必须在0到255之间
        value = int(segment)
        return 0 <= value <= 255

C++ 实现

cpp 复制代码
class Solution {
public:
    vector<string> restoreIpAddresses(string s) {
        vector<string> result;
        backtrack(s, 0, 0, "", result);
        return result;
    }
    
private:
    void backtrack(const string& s, int index, int segments, string current, vector<string>& result) {
        // 如果已经处理了4段且用完了所有字符,则找到一个有效的IP地址
        if (segments == 4 && index == s.length()) {
            // 移除最后一个点
            result.push_back(current.substr(0, current.length() - 1));
            return;
        }
        
        // 如果段数超过4或者已经处理完所有字符但段数不足4,则无效
        if (segments > 4 || index == s.length()) {
            return;
        }
        
        // 尝试分割出一个IP段(最多3位数字)
        for (int len = 1; len <= 3 && index + len <= s.length(); len++) {
            string segment = s.substr(index, len);
            
            // 检查IP段是否有效
            if (isValidSegment(segment)) {
                // 递归处理剩余部分
                backtrack(s, index + len, segments + 1, current + segment + ".", result);
            }
        }
    }
    
    bool isValidSegment(const string& segment) {
        // IP段不能有前导零,除非这个段就是0
        if (segment.length() > 1 && segment[0] == '0') {
            return false;
        }
        
        // IP段必须在0到255之间
        int value = stoi(segment);
        return value >= 0 && value <= 255;
    }
};

执行结果

C# 执行结果

  • 执行用时:92 ms,击败了 93.33% 的 C# 提交
  • 内存消耗:38.2 MB,击败了 90.00% 的 C# 提交

Python 执行结果

  • 执行用时:32 ms,击败了 95.24% 的 Python3 提交
  • 内存消耗:15.1 MB,击败了 92.86% 的 Python3 提交

C++ 执行结果

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:6.5 MB,击败了 94.74% 的 C++ 提交

代码亮点

  1. 剪枝优化:通过检查 IP 段的有效性,我们可以提前剪枝,避免无效的递归。
  2. 边界条件处理:代码中详细处理了各种边界情况,如前导零和数值范围。
  3. 回溯实现清晰:回溯函数的实现清晰明了,易于理解和维护。
  4. 字符串操作高效:使用了高效的字符串操作方法,避免了不必要的字符串拼接。
  5. 参数传递优化:在 C++ 实现中,使用了常量引用来传递字符串参数,提高了效率。

常见错误分析

  1. 忽略前导零:IP 段不能有前导零,除非这个段就是 0。例如,"01" 不是有效的 IP 段。
  2. 数值范围检查:IP 段必须在 0 到 255 之间,超出范围的数值是无效的。
  3. 段数不足或过多:有效的 IP 地址必须恰好有四个段,不能多也不能少。
  4. 字符串解析错误:在将字符串转换为整数时,需要确保正确处理,避免溢出或格式错误。
  5. 回溯终止条件:需要正确设置回溯的终止条件,避免无限递归或漏解。

解法比较

解法 时间复杂度 空间复杂度 优点 缺点
回溯法 O(3^4) = O(81) O(4) 实现简单,可以找到所有解 在最坏情况下可能需要尝试很多无效的分割
迭代法 O(3^4) = O(81) O(1) 避免了递归调用的开销 实现复杂,不如回溯法直观

注意:时间复杂度是 O(3^4),因为我们最多需要在字符串中插入 3 个点,每个点有 3 种可能的位置(因为 IP 段最多 3 位数字),所以总共有 3^4 = 81 种可能的分割方式。但实际上,由于剪枝和 IP 段的有效性检查,实际的时间复杂度会小于这个理论上限。

相关题目

相关推荐
gorgor在码农40 分钟前
神经网络基础(NN)
人工智能·pytorch·python·深度学习·神经网络·机器学习
CHPCWWHSU1 小时前
vulkanscenegraph显示倾斜模型(5.4)-相机操纵器
c++·osg·vulkan·vsg
wei_work@1 小时前
【Python】编程50个经典操作
开发语言·python·php
weixin_404289021 小时前
自定义创建中间件出现的ImproperlyConfigured: WSGI application错误的坑
python·中间件·django
钢铁男儿1 小时前
Python数据模型(延伸阅读)
开发语言·python
杜子腾dd1 小时前
21.Excel自动化:如何使用 xlwings 进行编程
运维·python·自动化·excel·numpy·pandas
_extraordinary_1 小时前
dfs刷题矩阵搜索问题
算法·矩阵·深度优先
co0t1 小时前
排序算法复习
数据结构·算法·排序算法
ChoSeitaku2 小时前
NO.58十六届蓝桥杯备战|基础算法-枚举|普通枚举|二进制枚举|铺地毯|回文日期|扫雷|子集|费解的开关|Even Parity(C++)
c++·算法·蓝桥杯