给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: "25525511135"
输出:["255.255.11.135", "255.255.111.35"]
首先介绍下IPv4地址:
IPv4 地址由十进制数和点来表示,每个地址由4个十进制数组成,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1
;
同时注意,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01
是不合法的。
解题思路:
1. 暴力搜索:由于每个地址由4个0-255的十进制数组成,可以嵌套4个for循环,每个变量范围为[1, 3],当四个变量的和恰好
等于输入字符串的长度时,即初步找到了四个合适的十进制数,然后判断这四个数是否满足IPv4地址的要求:值域[0, 255],不以0开头。
2. 回溯法:从0开始遍历输入字符串s,当其对应的整型数有效时,进入递归过程,即用同样的方法处理剩下的字符串。
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
基本思想类同于:图的深度优先搜索, 二叉树的后序遍历
一 1. 回溯法的详细描述
详细的描述则为:
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。
2. 回溯法应用
当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。
它有“通用解题法”之美誉。
二. 回溯法实现 - 递归和递推(迭代)
回溯法的实现方法有两种:递归和递推(也称迭代)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。
【类比于图深度遍历的递归实现和非递归(递推)实现】
具体代码:
递推实现:
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
vector<string> result;
string oneResult;
for(int i=1;i<=3;++i)
for(int j=1;j<=3;++j)
for(int m=1;m<=3;++m)
for(int n=1;n<=3;++n)
{
if(i+j+m+n==s.size()){
int addres1=stoi(s.substr(0,i));
int addres2=stoi(s.substr(i,j));
int addres3=stoi(s.substr(i+j,m));
int addres4=stoi(s.substr(i+j+m,n));
if(addres1<=255&&addres2<=255&&addres3<=255&&addres4<=255){
oneResult = to_string(addres1)+'.'+to_string(addres2)+'.'+to_string(addres3)+'.'+to_string(addres4);
if(oneResult.size()==3 + s.size()){
result.push_back(oneResult);
}
}
}
}
return result;
}
};
下面给出迭代搜索法的测试结果:
递归法:
class Solution {
public:
bool isValid93(string s)
{
int num = atoi(s.c_str());
if (num >= 0 && num <= 255 && to_string(num) == s) //to_string(num) == s用来判断字符串首位是否为0
{
return true;
}
else
{
return false;
}
}
void dfs93(string s, int times, string tmp, vector<string>& result)
{
if (times == 0)
{
if (s.empty())
{
result.push_back(tmp);
}
}
else
{
for (int i = 1; i <= 3; i++)
{
if (s.size() >= i && isValid93(s.substr(0, i)))
{
if (times == 1)
{
dfs93(s.substr(i), times - 1, tmp + s.substr(0, i), result);
}
else
{
dfs93(s.substr(i), times - 1, tmp + s.substr(0, i) + ".", result);
}
}
}
}
}
vector<string> restoreIpAddresses(string s) {
vector<string> result;
int n = s.size();
dfs93(s, 4, "", result);
return result;
}
};
下面给出测试结果:
总结:通过对比时间和空间的消耗,能够发现迭代算法的效率要高于递归,但递归算法看起来要更简单些