数据机构与算法之深入解析“复原IP地址”的求解思路与算法示例
Posted Serendipity·y
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据机构与算法之深入解析“复原IP地址”的求解思路与算法示例相关的知识,希望对你有一定的参考价值。
一、题目要求
- 有效 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。例如:“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 中的任何数字,可以按任何顺序返回答案。
- 示例 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"]
- 提示:
-
- 0 <= s.length <= 20;
-
- s 仅由数字组成。
二、求解算法
① 回溯法
- 回溯算法事实上就是在一个树形问题上做深度优先遍历,因此首先需要把问题转换为树形问题,模拟一下如何通过指定的字符串 s 生成 IP 地址的过程,把树形图画出来。
- 在画树形图的过程中,一定会发现有些枝叶是没有必要的,把没有必要的枝叶剪去的操作就是剪枝,在代码中一般通过 break 或者 contine 和 return (表示递归终止)实现。
- 一开始,字符串的长度小于 4 或者大于 12 ,一定不能拼凑出合法的 ip 地址(这一点可以一般化到中间结点的判断中,以产生剪枝行为);
- 每一个结点可以选择截取的方法只有 3 种:截 1 位、截 2 位、截 3 位,因此每一个结点可以生长出的分支最多只有 3 条分支;
- 根据截取出来的字符串判断是否是合理的 ip 段,这里写法比较多,可以先截取,再转换成 int ,再判断。我采用的做法是先转成 int,是合法的 ip 段数值以后,再截取;
- 由于 ip 段最多就 4 个段,因此这棵三叉树最多 4 层,这个条件作为递归终止条件之一;
- 每一个结点表示了求解这个问题的不同阶段,需要的状态变量有:
-
- splitTimes:已经分割出多少个 ip 段;
-
- begin:截取 ip 段的起始位置;
-
- path:记录从根结点到叶子结点的一个路径(回溯算法常规变量,是一个栈);
-
- res:记录结果集的变量,常规变量。
- Java 示例:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Stack;
public class Solution
public List<String> restoreIpAddresses(String s)
int len = s.length();
List<String> res = new ArrayList<>();
// 如果长度不够,不搜索
if (len < 4 || len > 12)
return res;
Deque<String> path = new ArrayDeque<>(4);
int splitTimes = 0;
dfs(s, len, splitTimes, 0, path, res);
return res;
/**
* 判断 s 的子区间 [left, right] 是否能够成为一个 ip 段
* 判断的同时顺便把类型转了
*
* @param s
* @param left
* @param right
* @return
*/
private int judgeIfIpSegment(String s, int left, int right)
int len = right - left + 1;
// 大于 1 位的时候,不能以 0 开头
if (len > 1 && s.charAt(left) == '0')
return -1;
// 转成 int 类型
int res = 0;
for (int i = left; i <= right; i++)
res = res * 10 + s.charAt(i) - '0';
if (res > 255)
return -1;
return res;
private void dfs(String s, int len, int split, int begin, Deque<String> path, List<String> res)
if (begin == len)
if (split == 4)
res.add(String.join(".", path));
return;
// 看到剩下的不够了,就退出(剪枝),len - begin 表示剩余的还未分割的字符串的位数
if (len - begin < (4 - split) || len - begin > 3 * (4 - split))
return;
for (int i = 0; i < 3; i++)
if (begin + i >= len)
break;
int ipSegment = judgeIfIpSegment(s, begin, begin + i);
if (ipSegment != -1)
// 在判断是 ip 段的情况下,才去做截取
path.addLast(ipSegment + "");
dfs(s, len, split + 1, begin + i + 1, path, res);
path.removeLast();
- Python 示例:
from typing import List
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
size = len(s)
if size < 4 or size > 12:
return []
path = []
res = []
self.__dfs(s, size, 0, 0, path, res)
return res
def __dfs(self, s, size, split_times, begin, path, res):
if begin == size:
if split_times == 4:
res.append('.'.join(path))
return
if size - begin < (4 - split_times) or size - begin > 3 * (4 - split_times):
return
for i in range(3):
if begin + i >= size:
break
ip_segment = self.__judge_if_ip_segment(s, begin, begin + i)
if ip_segment != -1:
path.append(str(ip_segment))
self.__dfs(s, size, split_times + 1, begin + i + 1, path, res)
path.pop()
def __judge_if_ip_segment(self, s, left, right):
size = right - left + 1
if size > 1 and s[left] == '0':
return -1
res = int(s[left:right + 1])
if res > 255:
return - 1
return res
- 与上面的示例代码不同之处只在于剪枝少判断,而且也是先判断截取的 ip 段是否合法,然后用截取函数截取字符串,执行结果上会快一些:
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
public class Solution
public List<String> restoreIpAddresses(String s)
int len = s.length();
List<String> res = new ArrayList<>();
if (len > 12 || len < 4)
return res;
Deque<String> path = new ArrayDeque<>(4);
dfs(s, len, 0, 4, path, res);
return res;
// 需要一个变量记录剩余多少段还没被分割
private void dfs(String s, int len, int begin, int residue, Deque<String> path, List<String> res)
if (begin == len)
if (residue == 0)
res.add(String.join(".", path));
return;
for (int i = begin; i < begin + 3; i++)
if (i >= len)
break;
if (residue * 3 < len - i)
continue;
if (judgeIpSegment(s, begin, i))
String currentIpSegment = s.substring(begin, i + 1);
path.addLast(currentIpSegment);
dfs(s, len, i + 1, residue - 1, path, res);
path.removeLast();
private boolean judgeIpSegment(String s, int left, int right)
int len = right - left + 1;
if (len > 1 && s.charAt(left) == '0')
return false;
int res = 0;
while (left <= right)
res = res * 10 + s.charAt(left) - '0';
left++;
return res >= 0 && res <= 255;
② 回溯法(LeetCode 官方解法)
- 由于需要找出所有可能复原出的 IP 地址,因此可以考虑使用回溯的方法,对所有可能的字符串分隔方式进行搜索,并筛选出满足要求的作为答案。
- 设题目中给出的字符串为 s,用递归函数 dfs(segId,segStart) 表示正在从 s[segStart] 的位置开始,搜索 IP 地址中的第 segId 段,其中 segId∈0,1,2,3。由于 IP 地址的每一段必须是 [0,255] 中的整数,因此从 segStart 开始,从小到大依次枚举当前这一段 IP 地址的结束位置 segEnd。如果满足要求,就递归地进行下一段搜索,调用递归函数 dfs(segId+1,segEnd+1)。
- 特别地,由于 IP 地址的每一段不能有前导零,因此如果 s[segStart] 等于字符 0,那么 IP 地址的第 segId 段只能为 0,需要作为特殊情况进行考虑。
- 在搜索的过程中,如果已经得到了全部的 4 段 IP 地址(即 segId=4),并且遍历完了整个字符串(即 segStart=∣s∣,其中 ∣s∣ 表示字符串 s 的长度),那么就复原出了一种满足题目要求的 IP 地址,将其加入答案。在其它的时刻,如果提前遍历完了整个字符串,那么需要结束搜索,回溯到上一步。
- Java 示例:
class Solution
static final int SEG_COUNT = 4;
List<String> ans = new ArrayList<String>();
int[] segments = new int[SEG_COUNT];
public List<String> restoreIpAddresses(String s)
segments = new int[SEG_COUNT];
dfs(s, 0, 0);
return ans;
public void dfs(String s, int segId, int segStart)
// 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if (segId == SEG_COUNT)
if (segStart == s.length())
StringBuffer ipAddr = new StringBuffer();
for (int i = 0; i < SEG_COUNT; ++i)
ipAddr.append(segments[i]);
if (i != SEG_COUNT - 1)
ipAddr.append('.');
ans.add(ipAddr.toString());
return;
// 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if (segStart == s.length())
return;
// 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if (s.charAt(segStart) == '0')
segments[segId] = 0;
dfs(s, segId + 1, segStart + 1);
// 一般情况,枚举每一种可能性并递归
int addr = 0;
for (int segEnd = segStart; segEnd < s.length(); ++segEnd)
addr = addr * 10 + (s.charAt(segEnd) - '0');
if (addr > 0 && addr <= 0xFF)
segments[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
else
break;
- Python 示例:
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
SEG_COUNT = 4
ans = list()
segments = [0] * SEG_COUNT
def dfs(segId: int, segStart: int):
# 如果找到了 4 段 IP 地址并且遍历完了字符串,那么就是一种答案
if segId == SEG_COUNT:
if segStart == len(s):
ipAddr = ".".join(str(seg) for seg in segments)
ans.append(ipAddr)
return
# 如果还没有找到 4 段 IP 地址就已经遍历完了字符串,那么提前回溯
if segStart == len(s):
return
# 由于不能有前导零,如果当前数字为 0,那么这一段 IP 地址只能为 0
if s[segStart] == "0":
segments[segId] = 0
dfs(segId + 1, segStart + 1)
# 一般情况,枚举每一种可能性并递归
addr = 0
for segEnd in range(segStart, len(s)):
addr = addr * 10 + (ord(s[segEnd]) - ord("0"))
if 0 < addr <= 0xFF:
segments[segId] = addr
dfs(segId + 1, segEnd + 1)
else:
break
dfs(0, 0)
return ans
以上是关于数据机构与算法之深入解析“复原IP地址”的求解思路与算法示例的主要内容,如果未能解决你的问题,请参考以下文章
数据机构与算法之深入解析“柱状图中最大的矩形”的求解思路与算法示例