【LeetCode回溯算法#06】复原IP地址详解(练习如何处理边界条件,判断IP合法性)
复原IP地址
给定一个只包含数字的字符串,复原它并返回所有可能的 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 地址。
示例 1:
- 输入:s = "25525511135"
- 输出:["255.255.11.135","255.255.111.35"]
示例 2:
- 输入:s = "0000"
- 输出:["0.0.0.0"]
示例 3:
- 输入:s = "1111"
- 输出:["1.1.1.1"]
示例 4:
- 输入:s = "010010"
- 输出:["0.10.0.10","0.100.1.0"]
示例 5:
- 输入: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 <= 3000
- s 仅由数字组成
思路
实际上还是分割问题,但是又多了一些条件
本题的麻烦的点在于有很多边界条件需要处理
待解决的问题有:
1、如何加入分割点
2、如何判断分割的段落是否为合法IP
插入分割点
"分割"这个动作与 分割回文串 里定义的一致,都是用beginIndex指到分割位置
不同的是,这里我们beginIndex指到分割的位置时,我们需要在这个位置的后一位插入分割点
(后面细说)
合法IP判断
这里我们需要写一个判断函数来判断IP 是否合法,函数的输入是字符串和待判断的区间
bool isVaild(string& s, int start, int end){
}
这里本质上我们还是在s上操作,只是指定了操作的位置
首先需要明确非法IP的情况:
0、所给的判断区间不合法
1、以0开头的数字,例如:192 . 031 . 2 . 1
2、负数
3、IP段数值大于255
bool isVaild(string& s, int start, int end){
if(start > end) return false;//所给的判断区间不合法
//是否以0开头
if(s[start] == '0' && start != end) return false;
int num4IP = 0;
for(int i = start; i < end; ++i){
//是否为正数
if(s[i] < 0 && s[i] > 9) return false;
//IP段数值是否大于255
num4IP = num4IP * 10 + (s[i] - '0');
if(num4IP > 255) return false;
}
return true;
}
这里有几个关键点:
1、判断是否有IP段以0开头
请注意,我们这里判断的是false的情况,也就是有IP段使用了0开头
判断条件除了直接看开头字符是否为0(s[start] == '0')以外,还需要确保当前IP段的数字不是第一个获取到的,因此需要追加条件 start != end
什么意思呢?这里又涉及到单层处理中的逻辑
在单层处理时,我们会去遍历由beginIndex控制的s中的区间,每次遍历都会触发isVaild函数来判断当前遍历所得的IP段是否合法
isVaild函数需要输入一个判断区间(这个区间其实就是[beginIndex, i])
当第一次遍历的时候,输入isVaild的区间是[0, 0],此时会出现一种特殊情况
例如,我们的数字字符串是:s = "0000"
那么第一次遍历获取IP段时,我们得到的是'0',从我们的角度来看,这种情况应该是合法的
如果我们不追加条件去判断当前遍历是不是第一次遍历,那么上述情况会被程序视为非法,就会导致错误
为什么start != end可以判断遍历是否为第一次遍历?
前面也说过了,因为我们输入的区间实际上是[beginIndex, i],那么这个区间只有在第一次遍历时才会出现左右边界相等的情况,即[0, 0]
2、判断IP段数值大于255
这里容易犯的一个错误是直接拿s[i]来和255进行大小判断
拜托,s[i]是什么?它是组成当前IP段的数字之一,单个数字怎么可能大于255,这样判断只会通过所有的情况
因此,这里需要一点小小的计算,我来模拟一下这个过程
int num4IP = 0;
for(int i = start; i < end; ++i){
//是否为正数
if(s[i] < 0 && s[i] > 9) return false;
//IP段数值是否大于255
num4IP = num4IP * 10 + (s[i] - '0');
if(num4IP > 255) return false;
}
例如当前的IP段是164,
第一轮遍历拿到s[i]是1,此时num = 0 * 10 + ('2' - '0') = 1
第二轮遍历拿到s[i]是6,此时num = 1 * 10 + ('5' - '0') = 16
第三轮遍历拿到s[i]是4,此时num = 16 * 10 + ('4' - '0') = 164
164显然是满足条件的
发现没有, num = num * 10 + (s[i] - '0');的作用就是把数据类型为字符串的IP段转化为整型,然后再判断
代码分析
开始写,还是老一套,回溯三部曲
1、确定回溯函数的参数和返回值
本题中我们是直接操作的数字字符串s,因此不需要返回值
输入参数是数字字符串s、beginIndex和countPoint
countPoint用于统计目前一共插入了几个分割点,用于终止判定
class Solution {
private:
bool isVaild(string& s, int start, int end){
...
}
vector<string> res;
//确定回溯函数的参数和返回值
//参数:数字字符串、beginIndex、分割点计数变量
void backtracking(string& s, int beginIndex, int countPoint){
}
public:
vector<string> restoreIpAddresses(string s) {
}
};
2、确定终止条件
这里就用到countPoint了
参考IP的结构,当我们已经插入了3个分割点时,这时需要结束了(不论之后还剩什么)
同时,我们还要判断一下被分割出来的第四段是否合法,合法就把当前插好的数字字符串s保存到结果数组(一定记得我们是对s进行操作,最后的结果也是处理好的s)
class Solution {
private:
bool isVaild(string& s, int start, int end){
...
}
vector<string> res;
//确定回溯函数的参数和返回值
//参数:数字字符串、beginIndex、分割点计数变量
void backtracking(string& s, int beginIndex, int countPoint){
if(countPoint == 3){
if(isVaild(s, beginIndex, s.size() - 1)){//判断第四段是否合法
res.push_back(s);
}
return;
}
}
public:
vector<string> restoreIpAddresses(string s) {
}
};
3、确定单层处理逻辑
在单层递归中,我们需要遍历由beginIndex控制的当前区间
使用for循环不断获取IP段,然后判断其是否合法
合法就在当前位置 i 的后一个位置插入分割点
class Solution {
private:
bool isVaild(string& s, int start, int end){
...
}
vector<string> res;
//确定回溯函数的参数和返回值
//参数:数字字符串、beginIndex、分割点计数变量
void backtracking(string& s, int beginIndex, int countPoint){
//确定终止条件
//当分割点的数量达到3个时说明分割结束,此时字符串已经被分成4段
//在这里还需要验证最后一段是否合法
if(countPoint == 3){
if(isVaild(s, beginIndex, s.size() - 1)){//判断第四段是否合法
res.push_back(s);
}
return;
}
//确定单层处理逻辑
for(int i = beginIndex; i < s.size(); ++i){
//这里beginIndex不是固定的,下一层递归时beginIndex会由上一层递归传入
if(isVaild(s, beginIndex, i)){
s.insert(s.begin() + i + 1, '.');//插入分割点
countPoint++;//计数++
backtracking(s, i + 2, countPoint);//只加1的话就到分割点的位置,所以得多加一个
countPoint--;//回溯
s.erase(s.begin() + i + 1);
}
}
}
public:
vector<string> restoreIpAddresses(string s) {
}
};
这里又涉及到一个边缘处理问题:插入分割点的位置
题外话:insert函数
本题中使用insert来在s中插入分割点
举个例子说明insert的使用:
teststr = "1234";
teststr.insert(1, '.');//在原串下标为1的字符1前插入'.'->"1.234"
所以为什么插分割点的时候的位置是s.begin() + i + 1
因为s.begin() + i只定位到了当前指针 i 遍历到的位置,而我们希望在该位置之后加点,所以要再加1
例如遍历"1234",遍历时 i 指向2时,即下标2,我们需要在2后面打点,此时要输入insert的应该是下标3而不是下标2
完整代码
本题思路很清晰,但是难点在于有很多的边界条件
一旦处理不好或者忘了,就会出错
class Solution {
private:
bool isVaild(string& s, int start, int end){
//判断区间是否合法
if(start > end) return false;
//判断数字开头是否为0,为0非法
if(s[start] == '0' && start != end){
return false;
}
int num4IP = 0;
//遍历待判断区间
for(int i = start; i <= end; ++i){
//是否为正数
if(s[i] > '9' || s[i] < '0'){
return false;
}
//是否在255范围内
num4IP = num4IP * 10 + (s[i] - '0');
if(num4IP > 255){
return false;
}
}
return true;
}
vector<string> res;
//确定回溯函数的参数和返回值
//参数:数字字符串、beginIndex、分割点计数变量
void backtracking(string& s, int beginIndex, int countPoint){
//确定终止条件
//当分割点的数量达到3个时说明分割结束,此时字符串已经被分成4段
//在这里还需要验证最后一段是否合法
if(countPoint == 3){
if(isVaild(s, beginIndex, s.size() - 1)){//判断第四段是否合法
res.push_back(s);
}
return;
}
//确定单层处理逻辑
for(int i = beginIndex; i < s.size(); ++i){
//这里beginIndex不是固定的,下一层递归时beginIndex会由上一层递归传入
if(isVaild(s, beginIndex, i)){
s.insert(s.begin() + i + 1, '.');//插入分割点
countPoint++;//计数++
backtracking(s, i + 2, countPoint);//只加1的话就到分割点的位置,所以得多加一个
countPoint--;//回溯
s.erase(s.begin() + i + 1);
}
}
}
public:
vector<string> restoreIpAddresses(string s) {
//这里可以先判断一下字符串s是否合法
if(s.size() < 4 || s.size() > 12) return res;
backtracking(s, 0, 0);
return res;
}
};
【LeetCode回溯算法#06】复原IP地址详解(练习如何处理边界条件,判断IP合法性)的更多相关文章
- IP地址和子网划分学习笔记之《IP地址详解》
2018-05-03 18:47:37 在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文. ...
- 计算机网络基础之IP地址详解
计算机网络基础之IP地址详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.IP地址概述 1>.什么是IP地址 我们为什么要使用逻辑地址(IP地址)来标识网络设备,而不采 ...
- Windows Azure IP地址详解
Windows Azure上的IP地址有以下几种: 公网IP地址 VIP ILPIP Reserved IP 内网IP地址 DIP Static IP VIP是动态分配的公网IP,VIP可以被分配到云 ...
- 【整理】PHP获取客户端真实IP地址详解
php获取客户端IP地址有四种方法,这五种方法分别为REMOTE_ADDR.HTTP_CLIENT_IP.HTTP_X_FORWARDED_FOR.HTTP_VIA. REMOTE_ADDR 是你的客 ...
- 如何设置CentOS 7开机自动获取IP地址详解
本例中以CentOS 7举例说明如何设置Linux开机自动获取IP地址和设置固定IP地址. 自动获取动态IP地址 1.输入“ip addr”并按回车键确定,发现无法获取IP(CentOS 7默认没有i ...
- Vue实战041:获取当前客户端IP地址详解(内网和外网)
前言 我们经常会有需求,希望能获取的到当前用户的IP地址,而IP又分为公网ip(也称外网)和私网IP(也称内网IP),IP地址是IP协议提供的一种统一的地址格式,每台设备都设定了一个唯一的IP地址”, ...
- IP地址详解
讲之前了解一些网络设备的作用: 交换机:组建局域网 路由器:连接内外网 网关:一个网络的出口(Gate Way = GW)一般网关在路由器上 局域网(也称内网) 一个简单的局域网的基本组成设备:交换机 ...
- Day02_IP地址详解&进制转换&DOS基本命令与批处理
学于千峰教育开源课程 感谢 千峰教育官网 b站在线视频 IP地址详解 一.简单局域网的构成 局域网:一般称为内网 简单局域网的构成:交换机.网线.PC(其他IT终端) 交换机:用来组建内网的局域网的设 ...
- TCP/IP协议详解概述
TCP/IP协议详解卷1--第一章概述--读书笔记 作者:vpoet 日期:2015/06/25 注:本系列的文章只是作者对TCP/IP协议的理解,难免会出现纰漏或者不完整,当然也有可能很肤浅,希望大 ...
- IP协议详解(转)
本文转载自博文协议森林05 我尽力 (IP协议详解).这篇博文写的很有趣味,特转载! IPv4与IPv6头部的对比 我们已经在IP接力中介绍过,一个IP包分为头部(header)和数据(payload ...
随机推荐
- 有关插槽中scope的问题
暂时不理解
- oracle常用知识随笔
1.创建表空间及用户赋权 create tablespace spaceone datafile '/dev/spaceone'size 80mextent management localsegme ...
- 完整的javaweb文档
1.index.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8&qu ...
- spider_爬取内涵吧的段子(二级深度爬取)
'''爬取内涵吧段子所有笑话(带标题,作者)总结:解码上,使用gbk2312编码的,我们可以采取gbk解码. 不会报错'''from fake_useragent import FakeUserAge ...
- 在安装SDK8.1和Visual Studio 2017时,提示“已停止工作”
解决办法:在微软官网下载 .net framework 的最新的开发包(Build apps - Dev Pack),重新安装后问题得到解决. https://dotnet.microsoft.com ...
- 射频识别技术(RFID)
概述: 无线射频识别即射频识别技术(Radio Frequency Identification,RFID),是自动识别技术的一种,通过无线射频方式进行非接触双向数据通信,利用无线射频方式对记录媒体( ...
- .NET CORE 下收发邮件之 MAILKIT
背景 利用代码发送邮件在工作中还是比较常见的,相信大家都用过SmtpClient来处理发送邮件的操作,不过这个类以及被标记已过时,所以介绍一个微软推荐的库MailKit来处理. MailKit开源地址 ...
- 如何查看win10的版本号。
1.设置--系统--关于 2..dxdiag 3.CMD中输入:slmgr /dlv 4.Winver
- 笔记:vue.nextTick()方法的使用详解
vue.nextTick定义: 在下次DOM更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的DOM. 简单理解:数据更新了,在dom渲染后立即执行该函数 举例 注意:Vue实 ...
- 【APT】海莲花组织DLL样本分析
前言 样本来源Twitter,之前的文章已经完整分析过一个类似的DLL样本,这次做个简单记录. 样本分析 样本信息如下: DLL文件共有40个导出函数: 导出函数内容基本一致,恶意代码都在DllMai ...