首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

LeetCode第93题:复原IP地址

  • 25-04-18 21:09
  • 3235
  • 13975
juejin.cn

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 restoreIpAddresses(string s) { vector result; backtrack(s, 0, 0, "", result); return result; } private: void backtrack(const string& s, int index, int segments, string current, vector& 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 段的有效性检查,实际的时间复杂度会小于这个理论上限。

相关题目

  • LeetCode 131. 分割回文串
  • LeetCode 17. 电话号码的字母组合
  • LeetCode 22. 括号生成
  • LeetCode 46. 全排列
注:本文转载自juejin.cn的旧厂街小江的文章"https://juejin.cn/post/7485640765570793524"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

143
阅读
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top