解码ip题解

一、题目描述:

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、关于题解

我的代码能力很弱,又懒得动手,结果就成了而恶型循环。之后写的每道题,我都会写一个题解,强迫自己加深记忆,理清思路。

以及很重要的一个收获是关于如何使用测试数据,内容我写到了另一篇文章《oj使用技巧》上面,有兴趣的话可以点击我的主页看一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值