一、题目描述:
Description
小明是一个解密爱好者,有一天他正在学校图书馆阅读《计算机网络》,突然书中掉落出一张纸片,上面写着一串数字,小明灵机一动猜想这有可能是一串ipv4地址。
《计算机网络》书中介绍到:一个有效的ipv4地址正好有4个整数(每个整数由0到255之间的整数组成,并且不能含有前导0),整数之间用“。”分隔。
例如“0.1.201.2”和“192.168.2.1”是有效的地址,但是“0.0.01.02”和“299.12&1.1”是无效的地址。
同学们能帮帮小明将这串数字解码为ipv4地址吗?
Input
第一行输入一个整数T,代表有T个测试用例
接下来有T行,每一行仅由数字组成,每一行长度范围为[1,12]
Output
对于每一行输入,输出可能的有效ipv4地址,每种可能之间用空格隔开,每一行最后一个地址后面没有空格,每个地址按照字典序升序排序后输出
若不能解码出有效的ipv4地址,则输出 -1
example
input
3
25525511135
010010
110
output
255.255.11.135 255.255.111.35 0.10.0.10 0.100.1.0 -1
二、解题思路:
1、处理IP函数 GetIp
vector<int> ip;//记录成功的一种情况
vector<vector<int>> res;//记录结果
void GetIp(vector<int>& before, int n, int sec)//n记录数字串处理的进度(第n位没有处理过),sec记录IP地址记录的进度(即将处理sec)
{
//判断进位
//若所剩的位数比ip所剩的空间少,则不可能解码成功;
//或所剩的位数比ip所剩的空间的最大容量还多,则也不可能解码成功
if ((before.size() - n) < (5 - sec) || (before.size() - n) > 3 * (5 - sec))
;
//cout << "位数错误 n=" << n << "sec=" << sec << endl;
//已经处理完了
else if (sec > 4)
{
res.push_back(ip);
}
//仍需要处理
else
{
//cout << "n=" << n << "sec=" << sec << endl;
//前导为零
if (before[n] == 0)
{
ip.push_back(0);//不能含有前导0
GetIp(before, n + 1, sec + 1);//递归调用,继续处理之后的数字串
ip.pop_back();//把ip还原到上一个结点,以便下一分支使用
}
else //前导不为0
{
//遍历在这一部分的所有情况,在该处最大位数为3,且总位数不能越界
for (int i = 1; i <= 3 && n + i <= before.size(); i++)
{
int num = 0;//保存数字串的整数形式
for (int j = 0; j < i; j++)//将数字串转为int型
{
num = num * 10 + before[n + j];
}
if (num <= 255)//符合条件,记录ip
{
ip.push_back(num);
GetIp(before, n + i, sec + 1);//递归调用,继续处理之后的数字串
ip.pop_back();//把ip还原到上一个结点,以便下一分支使用
}
}
}
}
}
主要分为三个部分
1、判断位数是否合理
2、判断是否处理完了ip的4部分
3、还需要进行处理
设置了两个全局vector,用于保存结果。
在实现过程中使用递归,比较重要的部分是由于ip是循环使用的,所以在每次递归调用之后记得要将刚刚放入的部分弹出。
有一个非常容易出错的点:在处理ip的过程中,需要用到两个for循环,i、j的起始与边界值一定要看清楚,另外n+i与size比较是<还是<=一定要注意。
另外,一般递归实现的函数在参数列表里都会有保存当前进度的参数。
2、输出函数 PrintIp
void PrintIp(vector<vector<int>>& res)//输出ip
{
int p = 0;//作为标志,判断ip是否能解码
for (vector<vector<int>>::iterator it = res.begin(); it != res.end(); it++)//遍历储存的每个答案
{
if ((*it)[0] == 999)//在读入时设置的,两组读入数字串之间的分割符
{
if (p == 0)
cout << -1;
cout << endl;
p = 0;
if ((*it) != res.back())
it++;
}
else
{
if(p==1) cout << " ";//主要是为了满足“每一行最后一个地址后面没有空格”
for (int i = 0; i < it->size(); i++)
{
cout << (*it)[i];
if (i < it->size() - 1)
cout << ".";
}
p = 1;
}
}
}
解释一下以上代码看起来比较疑惑的部分:
1、p p的设置主要是为了判断ip是否解码成功
把p初始为0,输出一组数据后将p值改为1
读到分隔值999就说明一组数据读完了,
判断p是否仍为0,若为0,则说明与上一组数据没有成功解码出ip,输出-1,
之后再将p赋值为0;
2、 if (i < it->size() - 1)
cout << ".";
为了在两个数之间输出“.”,而不会在一串ip的末尾输出"."
3、if ((*it) != res.back())//主要是为了满足“每一行最后一个地址后面没有空格”
这个输出格式判断是我改了几次才想到的,希望自己可以记住
三、代码实现
#include <iostream>
#include <queue>
#include <vector>
#include <string>
using namespace std;
vector<int> ip;//记录成功的一种情况
vector<vector<int>> res;//记录结果
void GetIp(vector<int>& before, int n, int sec)//n记录数字串处理的进度(第n位没有处理过),sec记录IP地址记录的进度(即将处理sec)
{
//判断进位
//若所剩的位数比ip所剩的空间少,则不可能解码成功;
//或所剩的位数比ip所剩的空间的最大容量还多,则也不可能解码成功
if ((before.size() - n) < (5 - sec) || (before.size() - n) > 3 * (5 - sec))
;
//cout << "位数错误 n=" << n << "sec=" << sec << endl;
//已经处理完了
else if (sec > 4)
{
res.push_back(ip);
}
//仍需要处理
else
{
//cout << "n=" << n << "sec=" << sec << endl;
//前导为零
if (before[n] == 0)
{
ip.push_back(0);//不能含有前导0
GetIp(before, n + 1, sec + 1);//递归调用,继续处理之后的数字串
ip.pop_back();//把ip还原到上一个结点,以便下一分支使用
}
else //前导不为0
{
//遍历在这一部分的所有情况,在该处最大位数为3,且总位数不能越界
for (int i = 1; i <= 3 && n + i <= before.size(); i++)
{
int num = 0;//保存数字串的整数形式
for (int j = 0; j < i; j++)//将数字串转为int型
{
num = num * 10 + before[n + j];
}
if (num <= 255)//符合条件,记录ip
{
ip.push_back(num);
GetIp(before, n + i, sec + 1);//递归调用,继续处理之后的数字串
ip.pop_back();//把ip还原到上一个结点,以便下一分支使用
}
}
}
}
}
void PrintIp(vector<vector<int>>& res)//输出ip
{
int p = 0;//作为标志,判断ip是否能解码
for (vector<vector<int>>::iterator it = res.begin(); it != res.end(); it++)//遍历储存的每个答案
{
if ((*it)[0] == 999)//在读入时设置的,两组读入数字串之间的分割符
{
if (p == 0)
cout << -1;
cout << endl;
p = 0;
if ((*it) != res.back())
it++;
}
else
{
if(p==1) cout << " ";//主要是为了满足“每一行最后一个地址后面没有空格”
for (int i = 0; i < it->size(); i++)
{
cout << (*it)[i];
if (i < it->size() - 1)
cout << ".";
}
p = 1;
}
}
}
int main() {
int num;//数据组数
cin >> num;
for (int i = 0; i < num; i++)
{
vector<int> before;//记录读入初始数字串
string str;
cin >> str;
for (int j = 0; j < str.size(); j++)
{
before.push_back((int)str[j] - 48);//将char型转为int型
}
GetIp(before, 0, 1);//破解ip
if (num != num - 1)//同样是为了满足一些输出格式,在答案的结尾不再换行
{
vector<int> sign;//在读入时设置的,两组读入数字串之间的分割符
sign.push_back(999);
res.push_back(sign);
}
}
PrintIp(res);
return 0;
}
四、一些反思
这道题其实是一个月前老师布置的了,因为自己的极致拖延,拖到了现在。
通过这道题自己收获其实蛮多的,
1、动手敲代码之前一定要把思路理清楚。
之前有这个习惯,但不知道什么时候开始丢掉了,结果写比较复杂的题目的时候结构写的乱七八糟,后期打补丁根本无济于事,浪费了大量的时间。
2、边界问题
自己写代码几乎没有边界不出错的,每次都很急躁,只是说“不知道怎么回事,太奇怪了,明明。。。”但其实静下心去找,还是在细节的地方自己没有注意。
3、关于题解
我的代码能力很弱,又懒得动手,结果就成了而恶型循环。之后写的每道题,我都会写一个题解,强迫自己加深记忆,理清思路。