刷题记录 回溯算法-10:93. 复原 IP 地址

题目:93. 复原 IP 地址

有效 IP 地址 正好由四个整数(每个整数位于 0255 之间组成,且不能含有前导 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"]

提示:

  • 1 <= s.length <= 20
  • s 仅由数字组成

一、模式识别

1.问题分解

该问题即用回溯法遍历出所有的切割方式,

并逐个判断子串是否合法

因此该题可以分解为切割方式子串合法性判断两个子问题

2.切割问题

从搜索集看为等效的无重复选取无重复元素组合问题

从结果收集条件看为长度条件位置(终点)条件共同作用

所以有双重验证条件:

python 复制代码
if len(path) == 4:
            if start_idx == len(s):
                ans.append('.'.join(path))
            return

但本题无需长度剪枝

因为本题中合法的子串对长度也有要求

长度剪枝 的作用会被子串合法性判断剪枝的作用覆盖

3.子串合法性判断

总结下来共三个规则:

①以0为开头的子串不合法

②有非正整数字符不合法

③若子串表示的数字大于255不合法

每切割一个子串都需要验证子串的合法性:

(1)验证数字大小 and 对比数字位数与字符位数:

python 复制代码
def is_validIP(self, s):
        n = len(s)
        num = int(s)
        if num < 0  or num > 255:
            return False
        t = 0 if not num == 0 else 1
        while num:
            num //= 10
            t += 1
        if n > t:
            return False
        return True

其中对比数位的操作同时验证①和②两个规则

(2)逐位验证数字 and 验证数字大小:

python 复制代码
def is_validIP(self, s):
        n = len(s)
        if s[0] == '0' and len(s) > 1:
            return False
        num = 0
        for i in range(n):
            if not s[i].isdigit():
                return False
            num = num * 10 + int(s[i])
            if num > 255:
                return False
        return True

由于子串表示数字大小限制,

因此节点搜索集宽度上限为3:

python 复制代码
for i in range(start_idx, min(start_idx + 3, len(s))):

此外子串合法性判断还有个规律:

包含不合法子串的其他字符串也不合法

因此有:

python 复制代码
for i in range(start_idx, len(s)):
            ch = s[start_idx: i + 1]
            if self.is_validIP(ch):
                path.append(ch)
                self.backtracking(s, i + 1, path, ans)
                path.pop()
            else:
                break

这条规律与子串长度限制的作用相互覆盖,

选择其一即实现剪枝

二.代码实现

本题中存在字符串,

因此有传递数组拼接传递字符记录点数两种写法

写法一(传递数组拼接):

python 复制代码
class Solution:
    def is_validIP(self, s):
        n = len(s)
        num = int(s)
        if num < 0  or num > 255:
            return False
        t = 0 if not num == 0 else 1
        while num:
            num //= 10
            t += 1
        if n > t:
            return False
        return True

    def backtracking(self, s, start_idx, path, ans):
        if len(path) == 4:
            if start_idx == len(s):
                ans.append('.'.join(path))
            return
        
        for i in range(start_idx, len(s)):
            ch = s[start_idx: i + 1]
            if self.is_validIP(ch):
                path.append(ch)
                self.backtracking(s, i + 1, path, ans)
                path.pop()
            else:
                break

    def restoreIpAddresses(self, s: str) -> List[str]:
        ans = []
        self.backtracking(s, 0, [], ans)
        return ans

写法二(传递字符串记录点数):

python 复制代码
class Solution:
    def is_valid(self, s):
        n = len(s)
        if n == 0:
            return False
        num = 0
        if n > 1 and s[0] == '0':
            return False
        for i in range(n):
            if not s[i].isdigit():
                return False
            num = num * 10 + int(s[i])
        return 0 <= num <= 255

    def backtracking(self, s, start_idx, pointnum, path, ans):
        if pointnum == 3:
            if self.is_valid(s[start_idx:]):
                ans.append(path + s[start_idx:])
            return
        
        for i in range(start_idx, len(s)):
            ch = s[start_idx: i + 1]
            if self.is_valid(ch):
                self.backtracking(s, i + 1, pointnum + 1, path + ch + '.', ans)
            else:
                break

    def restoreIpAddresses(self, s: str) -> List[str]:
        ans = []
        self.backtracking(s, 0, 0, "", ans)
        return ans

此外,验证函数的逻辑可以写在for循环内部

写法三(for循环内部验证合法性):

python 复制代码
class Solution:
    def backtracking(self, s, start_idx, path, ans):
        if len(path) == 4:
            if start_idx == len(s):
                ans.append('.'.join(path))
            return
        
        for i in range(start_idx, len(s)):
            if s[start_idx] == '0' and i > start_idx:
                break
            # 长度决定的两种不可能完成路径的情况:
            # 剩余路径的最大容量少于剩余的数字:3 * (4 - len(path)) < (len(s) - i)
            # 剩余路径的最小需求多于剩余的数字:(4 - len(path)) > (len(s) - i)
            if 3 * (4 - len(path)) < (len(s) - i) or (4 - len(path)) > (len(s) - i):
                break
            ch = s[start_idx: i + 1]
            if int(ch) > 255:
                break
            path.append(ch)
            self.backtracking(s, i + 1, path, ans)
            path.pop()

    def restoreIpAddresses(self, s: str) -> List[str]:
        ans = []
        self.backtracking(s, 0, [], ans)
        return ans
相关推荐
卷无止境25 分钟前
写代码这件事,到底该讲究点什么?
后端·python
卷无止境31 分钟前
循环复杂度到底在算什么,Python 代码怎么才能写得让人一看就懂
后端·python
lpfasd12334 分钟前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
Dxy12393102161 小时前
Python项目打包成EXE完整教程(PyInstaller实战避坑)
开发语言·python
bamb002 小时前
一个项目带你入门AI应用开发01
python
0566462 小时前
Python康复训练——常用标准库
开发语言·python·学习
圣保罗的大教堂2 小时前
leetcode 3517. 最小回文排列 I 中等
leetcode
昆曲之源_娄江河畔2 小时前
Python如何安装flask, pymssql
开发语言·python·flask·pymssql
土豆.exe2 小时前
Fastjson2 2.0.53 哈希碰撞 RCE:从原理到三种打法
算法·哈希算法
0566462 小时前
Python康复训练——控制流与函数
开发语言·python·学习