- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
力扣题目链接(opens new window) 。
给定一个只包含数字的字符串,复原它并返回所有可能的 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:
示例 2:
示例 3:
示例 4:
示例 5:
提示:
实际上还是分割问题,但是又多了一些条件 。
本题的麻烦的点在于有很多边界条件需要处理 。
待解决的问题有:
1、如何加入分割点 。
2、如何判断分割的段落是否为合法IP 。
"分割"这个动作与 分割回文串 里定义的一致,都是用beginIndex指到分割位置 。
不同的是,这里我们beginIndex指到分割的位置时,我们需要在这个位置 的后一位 插入分割点 。
(后面细说) 。
这里我们需要写一个判断函数来判断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;
}
这里有几个 关键点 :
请注意,我们这里判断的是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] 。
这里容易犯的一个错误是直接拿 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来在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合法性)的文章就讲到这里了,如果你想了解更多关于【LeetCode回溯算法#06】复原IP地址详解(练习如何处理边界条件,判断IP合法性)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在我的类里面,我学习了 Prolog 回溯算法和 Rete forprop 算法,但我也被告知 Rete 可用于进行反向传播。 这是如何运作的?它在哪些方面与 Prolog 回溯相似/不同? 例如,这
两个 friend P1 和 P2 向共同的 friend P3 发送相同的消息 M。 然而由于一些网络损坏,P3 一次只能接收一个字符不知道接收到的字符是属于 P1 还是 P2。 此外,P3 可能会
我最近发了几个理解递归和回溯的问题,我觉得我现在得到了一些东西,并尝试编写一个测试,我确实解决了数独问题,但是当我以另一种格式编写代码时,代码卡了一会儿,返回False,说明这个问题无解。 grid
有人可以指导我或解释如何在 LISP 中执行回溯吗?任何示例或链接将不胜感激。我确实尝试过谷歌,但是他们都没有足够简单的例子让我理解。 谢谢 最佳答案 典型的方法是将不可变状态向下传递到调用堆栈,辅助
我正在使用 apache 2.2.14 运行 Backtrack 5 R2 (ubuntu) 的完全库存安装。我尝试运行一个简单的 index.html 文件,其中包含一些 javascript 代码
如何在 Javascript 中获取回溯? 理想的特征: 入口函数名称,或匿名函数的一些有意义的标识符, 每个级别的参数列表, 行号。 这可以用标准的 ECMAScript 完成吗? 如果没有,是否可
本文首发公众号:小码A梦 回溯算法是一种常见的算法,常见用于解决排列组合、排列问题、搜索问题等算法,在一个搜索空间中寻找所有的可能的解。通过向分支不断尝试获取所有的解,然后找到合适的
Python 是否支持为每个异常/引发/断言显示相同的自定义错误消息(无论代码在哪里中断)? 我目前对它的破解使用了一个装饰器。我有一个函数main它显示回溯很好,但我希望它也打印my_var (在函
输入: 3,4,8,7,3 5,S,7,2,3, 8,5,5,8,10 9,3,3,8,7 6,10,3,G,1 目标是找到从起点(S)到目标(G)的最佳路径。 我们可以向上、向下、向左、向右移动。
我想匹配一个包含“json”(出现超过 2 次)且两个“json”之间没有字符串“from”的字符串。 For example(what I want the string match or not)
我正在尝试使用回溯方法找到熄灯游戏的解决方案。我无法理解此过程的算法。我的方法是枚举从 0 到 2n2 - 1 的所有整数,并将每个整数转换为具有 n*n 位的二进制数。然后,将其分成n2个二进制数字
所以我正在阅读这本书《服从测试山羊》,在学习 Python 时我在第六章中遇到了一个问题。它说我应该能够运行我们在本章和前一章中设置的功能测试,没有错误;但是,我不断收到我不知道如何修复的回溯。 Tr
我需要一些关于 Android 日志文件反混淆的帮助。 问题是如果我有这样的异常: ... 10-16 10:03:10.488: E/AndroidRuntime(25723): Cau
我有一个看起来像这样的表: here | there | -------+-------+ {1,1} | {1,1} | {1,1} | {2,1} | {1,1} | {1,2} |
我写了一小段代码,它应该接受一个字符数组并让它看起来像计算机正在输入文本。很简单,对吧?但是当我运行它时,Terminal 告诉我: *** stack smashing detected ***:
Python 中的堆栈跟踪显示文件路径。有什么方法可以让它们显示完全限定的函数名称吗? 例子: class Foo(object): def bar(self): raise
我决定深入学习回溯的概念,我有以下任务: 给定N个投资者,M个城市,N×M个投资者偏好矩阵P(P[i,j]=1,当第i个投资者希望在第j个城市建矿池;P[i, j] = 0 那么他是中立的,当 P[i
设 E - 图 G 中所有边的集合问题是从G中找到顶点的最小子集S,它满足条件:S = E 中每个顶点的所有出边的总和 换句话说:边是街道,我们可以在顶点上放置路灯。如果我们在一个顶点上放置一盏路灯—
我正在尝试做这个我在查找面试问题时遇到的问题。我们被问及将 r 个硬币放置在 n*m 网格上的方法数量,使得每行和每列至少包含一个硬币。 我想到了一个回溯解决方案,按行主要顺序处理网格中的每个单元格,
我使用 DexGuard混淆。我有来自崩溃日志和映射文件的堆栈跟踪。当我运行 retrace.bat 并为其提供堆栈跟踪和映射文件时,输出仍然是混淆格式。 最佳答案 您是否在使用 ProGuard 的
我是一名优秀的程序员,十分优秀!