一、题目描述
有效 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 地址。
给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 ‘.’ 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135"
输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000"
输出:["0.0.0.0"]
示例 3:
输入:s = "101023"
输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
二、解题
回溯
这题就是有不少判断条件,首先是前导0的判定,出现了0,则直接进入回溯,还有ip地址的大小在0-255之间。
class Solution {
static final int SEG_COUNT = 4;
List<String> res = new ArrayList<String>();
int[] segments = new int[SEG_COUNT];
public List<String> restoreIpAddresses(String s) {
segments = new int[SEG_COUNT];
//开始回溯 从0下标开始遍历
dfs(s,0,0);
return res;
}
public void dfs(String s, int segId, int segStart) {
//首先写跳出终止条件
//如果到达了四段
if(segId == SEG_COUNT){
//如果遍历完了字符串
if(segStart == s.length()){
//拼接四段
StringBuilder sb = new StringBuilder();
for(int i = 0;i<SEG_COUNT;i++){
sb.append(segments[i]);
//添加 .
if(i != SEG_COUNT - 1){
sb.append('.');
}
}
res.add(sb.toString());
}
return ;
}
//如果没有找到四段地址就已经遍历完了字符串 提前回溯
if(segStart == s.length()){
return ;
}
//前导0判别
if(s.charAt(segStart) == '0'){
segments[segId] = 0;
dfs(s,segId+1,segStart+1);
}
//一般情况
int addr = 0;
for(int end = segStart;end<s.length();end++){
addr = addr * 10 +(s.charAt(end) - '0');
if(addr>0 && addr <= 255){
segments[segId] = addr;
dfs(s,segId+1,end+1);
}else{
break;
}
}
}
}
全排列
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> res = new ArrayList<>();
List<String> path = new ArrayList<>();
if(s == null){
return res;
}
dfs(s,res,path,0);
return res;
}
//DFS
public void dfs(String s,List<String> res,List<String> path,int start){
//终止条件
if(path.size() == 4){
if(start == s.length()){
//添加数据
StringBuilder sb = new StringBuilder();
for(int i = 0;i<path.size();i++){
sb.append(path.get(i));
if(i != 3){
sb.append('.');
}
}
res.add(sb.toString());
return ;
}else{
return;
}
}
//一般情况 分割字符串,然后判断这个字符串是否是有效的ip字段
for(int end = start;end<s.length();end++){
if(isValidIp(s,start,end)){
//如果是有效的ip字段
// 添加字段
path.add(s.substring(start,end+1));
dfs(s,res,path,end+1);
path.remove(path.size()-1);
}
}
}
public boolean isValidIp(String s,int left,int right){
if(left>right){
return false;
}
//ip字段长度不超过三位数
if(right - left > 3){
return false;
}
//前导0
if(s.charAt(left) == '0' && left != right){
return false;
}
// 判断这个字符串[left, right]是不是满足0, 255的范围
int sum = Integer.valueOf(s.substring(left, right + 1));
return sum >= 0 && sum <= 255 ? true : false;
}
}
class Solution {
List<String> result = new ArrayList();
List<String> ipString = new ArrayList();
public List<String> restoreIpAddresses(String s) {
dfs(s,0);
return result;
}
public void dfs(String source,int index){
if(ipString.size()==4){
if(index == source.length()){
StringBuilder sb = new StringBuilder();
for(int i=0;i<ipString.size();i++){
sb.append(ipString.get(i));
if(i!=ipString.size()-1){
sb.append(".");
}
}
result.add(sb.toString());
return;
} else{
return;
}
}
for(int i = index;i<source.length();i++){
if(isIP(source,index,i)){
ipString.add(source.substring(index,i+1));
dfs(source,i+1);
ipString.remove(ipString.size()-1);
}
}
}
public boolean isIP(String s,int left,int right){
if (left > right) {
return false;
}
if (right - left > 3) {
return false;
}
if (s.charAt(left) == '0' && left != right) {
return false;
}
// 判断这个字符串[left, right]是不是满足0, 255的范围
int ans = Integer.valueOf(s.substring(left, right + 1));
return ans >= 0 && ans <= 255 ? true : false;
}
}
class Solution {
List<String> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
//这里就是对字符串的预处理,但是对于测试用例来说我觉得用处不大,毕竟不会蠢到用13位数字让你分割
if(s.length()<4 || s.length()>12){
return res;
}
//这里就是套用最经典的回溯模板了,相比于分割字符串只加入分割线一个参数以外,这里还需要添加额外的层数参数level
//因为合法的IP地址只有四段,我们不能无限对其进行分割
backtracking(s,0,0);
return res;
}
// 判断分割出来的每一段字符串是否是合法的IP地址
boolean isValidIp(String s){
//判断其是否含有前导0
if(s.charAt(0)=='0' && s.length()>1){
return false;
}
//长度为4就直接舍弃,加上这一步是为了后面parseInt做准备,防止超过了Integer可以表示的整数范围
if(s.length()>3){
return false;
}
//将字符转为int判断是否大于255,因为题目明确说了只由数字组成,所以这里没有对非数字的字符进行判断
if(Integer.parseInt(s)>255){
return false;
}
return true;
}
void backtracking(String s,int splitIndex,int level){
//递归终止条件,分割的四个字符串都是合法的IP地址
if(level==4){
//在代码的最后再利用join函数加上“.”,构造IP地址的表示形式
res.add(String.join(".",path));
return;
}
for(int i=splitIndex;i<s.length();i++){
//每一次分割之后,对剩余字符长度是否合理进行判断,剪枝操作,优化运行速度
if((s.length()-i-1) > 3*(3-level)){
continue;
}
//如果分割的字符串不是合理的IP地址,跳过
if(! isValidIp(s.substring(splitIndex,i+1))){
continue;
}
//把合法的IP地址段加入path存储
path.add(s.substring(splitIndex,i+1));
//每次把分割线往后移一位,且段数level+1
backtracking(s,i+1,level+1);
//进行回溯操作
path.remove(path.size()-1);
}
}
}