回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
百度百科说的有点晦涩,就是递归算法,但是有要注意的点:
- 保留现场,在进行递归的时候,保存上一次的状态,递归回来之后仍然能回到上次的状态
- 结束条件,满足条件的时候保存结果
题目
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
复制代码
解答
我做的算法题不多,但是个人经验,在做题的时候最好能手动的在纸上画图,模拟程序运行的图。
- 一张大白纸
- 认真的画图
不过也可能再电脑上画图会更方便,我们先思考程序的运行大致应该是什么路径。如图:
限定条件:
- 因为是IP地址,所以有一定的条件限制,数字不能大于255,不能小于0
- 在图上可以看出来,得出是否是IP地址的在第3层。
- 先想到大概的变量。
- 层级
- 当前的字符串
- 剩余的字符串
- 保存结果的集合
- 写出终止递归的条件
private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List res) {
if (currentLevel == 3) {
if (isValidNew(source.substring(offset))
) {
res.add(currentStr + "." + source.substring(offset));
}
return;
}
....
}
复制代码
- 写出校验是否合法IP中段位的函数
private boolean isValidNew(String str) {
if (str.length() == 0 || str.length() > 3 || Integer.parseInt(str) < 0 || Integer.parseInt(str) > 255
|| (str.startsWith("0") && str.length() > 1)
) {
return false;
}
return true;
}
复制代码
- 写进行递归的条件
private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List res) {
if (currentLevel == 3) {
if (isValidNew(source.substring(offset))
) {
res.add(currentStr + "." + source.substring(offset));
}
return;
}
// 递归段
for (int i = 1; i <= 3; i++) {
if (offset > source.length() || (offset + i) > source.length()) {
return;
}
String seg = source.substring(offset, offset + i);
// 用于保存原先的状态
String oldStr = currentStr;
// 防止出现以 "."开头的地址
if (currentStr.length() == 0) {
currentStr = seg;
} else {
currentStr = currentStr + "." + seg;
}
if (isValidNew(seg)) {
restoreAddressNew(source, currentStr, currentLevel + 1, offset + i, res);
// 处理完之后恢复状态
currentStr = oldStr;
}
}
}
复制代码
- 如果有不确定的地方,在其中可以输出当前的状态,判断当前的结果
- 完整的方案
public class RestoreIPAddress {
public static void main(String[] args) {
List strings = new RestoreIPAddress().restoreIpAddressesNew("25525511135");
}
private List restoreIpAddressesNew(String s) {
List result = new ArrayList<>();
restoreAddressNew(s, "", 0, 0, result);
return result;
}
private void restoreAddressNew(String source, String currentStr, Integer currentLevel, Integer offset, List res) {
if (currentLevel == 3) {
if (isValidNew(source.substring(offset))
) {
res.add(currentStr + "." + source.substring(offset));
}
return;
}
for (int i = 1; i <= 3; i++) {
if (offset > source.length() || (offset + i) > source.length()) {
return;
}
String seg = source.substring(offset, offset + i);
String oldStr = currentStr;
if (currentStr.length() == 0) {
currentStr = seg;
} else {
currentStr = currentStr + "." + seg;
}
if (isValidNew(seg)) {
restoreAddressNew(source, currentStr, currentLevel + 1, offset + i, res);
currentStr = oldStr;
}
}
}
private boolean isValidNew(String str) {
if (str.length() == 0 || str.length() > 3 || Integer.parseInt(str) < 0 || Integer.parseInt(str) > 255
|| (str.startsWith("0") && str.length() > 1)
) {
return false;
}
return true;
}
}
复制代码
中间犯的错误
- 第一次忘了保存现场,就会出现”数据打架“,数据很奇怪
- 出现过"."开头的地址情况
都是想问题不够全面导致的,程序运行的快,人算的慢,但是程序运行的规则是人定义的。只有自己能想清楚规则,才能指导计算机做正确的事情,否则当自己都迷糊的时候,程序怎么可能运行的正确。
最后
画图是的个好东西,帮你理清思路,慢慢来,不要着急
参考
- LeetCode 93题