1. 题目
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 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 地址。
2. 思路
使用分支剪枝的的方式:
1、一开始,字符串的长度小于 4 或者大于 12 ,一定不能拼凑出合法的 ip 地址(这一点可以一般化到中间结点的判断中,以产生剪枝行为);
2、每一个结点可以选择截取的方法只有 3 种:截 1 位、截 2 位、截 3 位,因此每一个结点可以生长出的分支最多只有 3 条分支;
根据截取出来的字符串判断是否是合理的 ip 段,这里写法比较多,可以先截取,再转换成 int ,再判断。我采用的做法是先转成 int,是合法的 ip 段数值以后,再截取。
3、由于 ip 段最多就 4 个段,因此这棵三叉树最多 4 层,这个条件作为递归终止条件之一;
回溯算法事实上就是在一个树形问题上做深度优先遍历,因此 首先需要把问题转换为树形问题。有些枝叶是没有必要的,把没有必要的枝叶剪去的操作就是剪枝
3.实现
剪枝少判断,而且也是先判断截取的 ip 段是否合法,然后用截取函数截取字符串,执行结果上会快一些
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
//1. 输入数字字符串。数字字符串的长度要 >=4, 并且<=12
String str = sc.next();
while(str.length()<4 || str.length()>12) {
str = sc.next();
}
//2.定义队列数组,存放当前合法的ip字符串。例如 "255" "255" "111" "23"
Deque<String> path = new ArrayDeque<String>(4);
//3.结果集。存放获取的所有合法的ip地址
List<String> res = new ArrayList<String>();
//4.进行深度优先遍历,获取合法的ip地址。
// str:输入的数字字符串; 从0位置开始截取,当前要获取的ip地址还有4个数未获取;path:存放当前获取的ip字符串数组
dfs(str,str.length(),0,4,path,res);
System.out.println(res);
}
/*(1) 获取合法的ip地址(可有多个)
* begin:截取ip端的起始位置
* remain:记录当前的ip地址还有多少段未被分割;
* */
private static void dfs(String str, int len, int begin, int remain, Deque<String> path, List<String> res) {
// 1.如果已经遍历完数字字符串,并且获取了ip地址的4个段,则获取了一个合法的ip地址(递归的出口)
if(begin == len) {
if(remain == 0) {
// 将path数组中的4个段用.连接
res.add(String.join(".", path));
}
return;
}
// 2.依次遍历1、2、3位的数字
for(int i=begin;i<begin+3;i++) {
// 2.1 越界
if(i>=len) {
break;
}
// 2.2 保证未遍历的数字位数不能超过remain*3
if(remain*3<len-i) {
continue;
}
// 2.3 判断当前段是否合法.str中,下标begin到i之间的数字
if(judge(str,begin,i)) {
//将合法的数字段存入path最后
String curNum = str.substring(begin,i+1);
path.add(curNum);
//递归获取剩余的remain个字段
dfs(str,len,i+1,remain-1,path,res);
//回溯,将不合法的情况移除
path.removeLast();
}
}
}
/*(2)判断str中,下标left到right之间的数字是否合法
* */
private static boolean judge(String str, int left, int right) {
// 1.数字位数
int len = right-left+1;
// 2.如果数字大于1位时,第一个数不能是0
if(len>1 && str.charAt(left)=='0') {
return false;
}
// 3.将区间内的字符串转化为数字
int num=0;
for(int i=left;i<=right;i++) {
num=num*10+str.charAt(i)-'0';
}
// 4.判断数字是否在区间[0,255]
return num>=0 && num<=255;
}
}