贴题:
有效 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
中的任何数字。你可以按 任何 顺序返回答案。
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
1、解法
按照上文表述的回溯三部曲,第一步先画图,画出树的生成图
按照递归三部曲寻找递归条件
1、当标点符号"."为3的时候可构成返回路径
2、根据题意构造for循环,循环体内应该是从开始索引到len(s)的位置单层for循环的逻辑
3、随后返回回溯递归条件
2、逐次分析中发现问题中有约束条件
1、不能是0开头
2、不能有无效字符
3、在255范围之内
因此有辅助函数isTrue
func isTrue(s string, start, end int) bool {
if start > end || start >= len(s) || end >= len(s) {
return false
}
if s[start] == '0' && start != end {
return false
}
num := 0
for i := start; i <= end; i++ {
if s[i] > '9' || s[i] < '0' { // 遇到非数字字符不合法
return false
}
c := int(s[i] - '0')
num = num*10 + c
if num > 255 { // 如果大于255了不合法
return false
}
}
return true
}
由上述分析得知可以写出backtracking函数
func BackTracking(s string, startIndex int, pointNum int, segments []string) {
if pointNum == 3 {
if isTrue(s, startIndex, len(s)-1) {
segments = append(segments, s[startIndex:])
ipAddress := strings.Join(segments, ".")
result = append(result, ipAddress)
}
return
}
for i := startIndex; i < len(s); i++ {
if isTrue(s, startIndex, i) {
segment := s[startIndex : i+1]
segments = append(segments, segment)
pointNum++
BackTracking(s, i+1, pointNum, segments)
pointNum--
segments = segments[:len(segments)-1]
} else {
break
}
}
}
整个函数的分析如下:
var result []string
func restoreIpAddresses(s string) []string {
result = []string{} // 清空 result 数组,防止遗留上一次搜索的结果
BackTracking(s, 0, 0, []string{}) // 调用回溯函数
return result // 返回所有合法的 IP 地址
}
func BackTracking(s string, startIndex int, pointNum int, segments []string) {
// 如果已经找到了 3 个合法的子段,那么分割的字符串必须正好组成 4 段,才是一个符合规则的 IP 地址
if pointNum == 3 {
if isTrue(s, startIndex, len(s)-1) { // 如果最后一个子段合法,则将其加入到 segments 切片中,然后将其 join 起来得到一个合法的 IP 地址
segments = append(segments, s[startIndex:])
ipAddress := strings.Join(segments, ".")
result = append(result, ipAddress) // 将符合要求的 IP 地址加入到 result 数组中
}
return // 返回上一层(回溯)
}
// 枚举每个可以分割的位置,并递归下去
for i := startIndex; i < len(s); i++ {
if isTrue(s, startIndex, i) { // 判断当前分割出来的子段是否合法
segment := s[startIndex : i+1] // 如果合法,则将其添加到 segments 切片中
segments = append(segments, segment)
pointNum++ // 修改子段数量
BackTracking(s, i+1, pointNum, segments) // 递归下去
pointNum-- // 还原子段数量,以便继续搜索
segments = segments[:len(segments)-1] // 移除当前递归过程中添加的子段
} else {
break // 如果当前分割出来的子段不合法,则回溯上一层
}
}
}
func isTrue(s string, start, end int) bool {
// 判断子串的下标是否超出范围
if start > end || start >= len(s) || end >= len(s) {
return false
}
// 子串以 0 开头,但长度大于 1 时,不合法
if s[start] == '0' && start != end {
return false
}
num := 0
// 将子串转化为数字
for i := start; i <= end; i++ {
if s[i] > '9' || s[i] < '0' { // 遇到非数字字符不合法
return false
}
c := int(s[i] - '0')
num = num*10 + c
if num > 255 { // 如果大于 255 了不合法
return false
}
}
return true
}