题目来源
题目描述
class Solution {
public:
vector<string> restoreIpAddresses(string s) {
}
};
题目解析
需要解决两个问题:
- 怎么切割
- 子串是否合法、IP地址是否合法
分析
回溯的第一个要点:选择
- 以 “25525511135” 为例,第一步时我们有几种选择?
- 以“2”作为第一个片段
- 以“22”作为第一个片段
- 以“222”作为第一个片段
- 能切三种不同的长度,切第二个片段时,又面临三种选择
- 这时会向下分支,形成一棵树,我们用DFS去遍历所有选择,必要时提前回溯
- 因为某一步的选择可能是错的,得不到正确的结果,不要往下继续搜索了,撤销最后一个选择,回到选择前的状态,去试另一个选择
在回溯问题中,我们要明确每一步的选择是什么:
- 可以选择当前位置的一位数字,也可以选择两位数数字,也可以选择三位数字
回溯的第二个要点:约束
- 约束条件限制了当前的选项,这道题的约束条件是:
- 一个片段的长度是 1~3
- 片段的值范围是0~255
- 不能是“0x”、“0xx”形式
- 用这些约束进行剪枝,去掉一些选择,避免搜索[不会产生正确答案]的分支
回溯的第三个要点:目标
- 目标决定了什么时候捕获答案,什么时候砍掉死支,回溯。
- 目标是生成4个有效片段,并且要耗尽IP的字符串
- 当条件满足时,说明生成了一个有效组合,加入解集,结束当前递归,继续探索别的分支
- 如果满4个有效片段,但是没有耗尽字符,不是想要的题解,不继续往下递归,提前回溯
定义dfs函数
- dfs函数传什么?也就是,用什么描述一个节点的状态
- 选择切出一个判断后,继续递归剩余子串。可以传子串,也可以传指针,加上当前的片段数组,描述节点的状态
- dfs 函数做的事:复原从 start 到末尾的子串。
递归树:
- 如图
['2','5','5','2']
未耗尽字符,不是有效组合,不继续选下去。撤销选择"2"
,回到之前的状态(当前分支砍掉了),切入到另一个分支,选择"25"
。
- 上图展示找到一个有效的组合的样子。start 指针越界,代表耗尽了所有字符,且满 4 个片段。
class Solution {
static std::string joins(vector<string> &curr){
std::string tmp;
for(auto &t : curr){
tmp += t;
tmp += ".";
}
tmp.pop_back();
return tmp;
}
public:
vector<string> restoreIpAddresses(string s) {
vector<string> ans;
std::function<void(vector<string>, int )> dfs = [&](vector<string> curr, int start){
if(curr.size() == 4 && start == s.size()){ //temp中有4段且s结束
ans.emplace_back(joins(curr)); //ans中保存一种可行方案
return;
}
if(curr.size() == 4 && start < s.size()){ //s有字符没用完
return;
}
for (int len = 1; len <= 3; ++len) { // 枚举出选择,三种切割长度
if(start + len - 1 >= s.size()){ // 加上要切的长度就越界,不能切这个长度
return;
}
if(len != 1 && s[start] == '0'){ // 不能切出'0x'、'0xx'
return;
}
std::string str = s.substr(start, len); // 当前选择切出的片段
if(len == 3 && atoi(str.c_str()) > 255){ // 不能超过255
return;
}
curr.push_back(str); // 作出选择,将片段加入curr
dfs(curr, start + len); // 基于当前选择,继续选择,注意更新指针
curr.pop_back(); // 上面一句的递归分支结束,撤销最后的选择,进入下一轮迭代,考察下一个切割长度
}
};
std::vector<std::string> curr;
dfs(curr, 0);
return ans;
}
};
void print_vec(const vector<string>& vec){
for(const string& e: vec)
cout << e << " ";
cout << endl;
}
int main(){
string s1 = "25525511135";
print_vec(Solution().restoreIpAddresses(s1));
// 255.255.111.35 255.255.11.135
string s2 = "1";
print_vec(Solution().restoreIpAddresses(s2));
// empty
string s3 = "010010";
print_vec(Solution().restoreIpAddresses(s3));
// 0.10.0.10 0.100.1.0
return 0;
}
class Solution {
string get_string(const vector<int>& ip){
std::string res = std::to_string(ip[0]);
for (int i = 1; i < ip.size(); ++i) {
res += "." + std::to_string(ip[i]);
}
return res;
}
void dfs(const std::string &s, int index, std::vector<int> &ip, std::vector<std::string>& ans){
if(index == s.size()){ //搜索完毕
if(ip.size() == 4){ //发现生成了四段
ans.emplace_back(get_string(ip));
}
return;
}
if(index == 0){ //刚开始搜索
ip.push_back(s[0] - '0');
dfs(s, index + 1, ip, ans); // 继续搜索
}else{
int next = ip.back() * 10 + (s[index] - '0');
if(next <= 255 && ip.back() != 0){ //合法
ip.back() = next;
dfs(s, index + 1, ip, ans); //可以继续往深处搜索
ip.back() /= 10; // 恢复现场
}
if(ip.size() < 4){ //高度最多为4
ip.push_back(s[index] - '0');
dfs(s, index + 1, ip, ans);
ip.pop_back();
}
}
}
public:
vector<string> restoreIpAddresses(string s) {
vector<string> ans;
vector<int> ip;
dfs(s, 0, ip, ans);
return ans;
}
};
void print_vec(const vector<string>& vec){
for(const string& e: vec)
cout << e << " ";
cout << endl;
}
int main(){
string s1 = "25525511135";
print_vec(Solution().restoreIpAddresses(s1));
// 255.255.111.35 255.255.11.135
string s2 = "1";
print_vec(Solution().restoreIpAddresses(s2));
// empty
string s3 = "010010";
print_vec(Solution().restoreIpAddresses(s3));
// 0.10.0.10 0.100.1.0
return 0;
}
class Solution {
// 还剩下多少刀需要切割
void restore(string s, int k, string out, vector<string> &res) {
if (k == 0) {
if (s.empty()) res.push_back(out);
}else {
for (int i = 1; i <= 3; ++i) { // 对于当前,可以是1位、2位、3位
if (s.size() >= i && isValid(s.substr(0, i))) {
if (k == 1) restore(s.substr(i), k - 1, out + s.substr(0, i), res);
else restore(s.substr(i), k - 1, out + s.substr(0, i) + ".", res);
}
}
}
}
bool isValid(string s) {
if (s.empty() || s.size() > 3 || (s.size() > 1 && s[0] == '0')) return false;
int res = atoi(s.c_str());
return res <= 255 && res >= 0;
}
public:
vector<string> restoreIpAddresses(string s) {
vector<string> res;
restore(s, 4, "", res);
return res;
}
};
剪枝条件:
1、一开始,字符串的长度小于 4 或者大于 12 ,一定不能拼凑出合法的 ip 地址(这一点可以一般化到中间结点的判断中,以产生剪枝行为);
2、每一个结点可以选择截取的方法只有 3 种:截 1 位、截 2 位、截 3 位,因此每一个结点可以生长出的分支最多只有 3 条分支;
3、根据截取出来的字符串判断是否是合理的 ip 段,这里写法比较多
4、由于 ip 段最多就 4 个段,因此这棵三叉树最多 4 层,这个条件作为递归终止条件之一;
类似题目
题目 | 思路 |
---|---|
leetcode:751. IP 到 CIDR | 这道题没有看懂 |
leetcode:93. 复原 IP 地址 Restore IP Addresses | |
leetcode:1108. IP 地址无效化 | |
leetcode:468. 验证IP地址 Validate IP Address |