给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 "IPv4" ;如果是有效的 IPv6 地址,返回 "IPv6" ;如果不是上述类型的 IP 地址,返回 "Neither" 。
有效的IPv4地址 是 "x1.x2.x3.x4" 形式的IP地址。 其中 0 <= xi <= 255 且 xi 不能包含 前导零。例如: "192.168.1.1" 、 "192.168.1.0" 为有效IPv4地址, "192.168.01.1" 为无效IPv4地址; "192.168.1.00" 、 "192.168@1.1" 为无效IPv4地址。
一个有效的IPv6地址 是一个格式为"x1:x2:x3:x4:x5:x6:x7:x8" 的IP地址,其中:
1 <= xi.length <= 4
xi 是一个 十六进制字符串 ,可以包含数字、小写英文字母( 'a' 到 'f' )和大写英文字母( 'A' 到 'F' )。
在 xi 中允许前导零。
例如 "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 和 "2001:db8:85a3:0:0:8A2E:0370:7334" 是有效的 IPv6 地址,而 "2001:0db8:85a3::8A2E:037j:7334" 和 "02001:0db8:85a3:0000:0000:8a2e:0370:7334" 是无效的 IPv6 地址。
示例 1:
输入:queryIP = "172.16.254.1"
输出:"IPv4"
解释:有效的 IPv4 地址,返回 "IPv4"
示例 2:
输入:queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334"
输出:"IPv6"
解释:有效的 IPv6 地址,返回 "IPv6"
示例 3:
输入:queryIP = "256.256.256.256"
输出:"Neither"
解释:既不是 IPv4 地址,又不是 IPv6 地址
提示:
queryIP 仅由英文字母,数字,字符 '.' 和 ':' 组成。
好题!验证IP地址需要同时处理IPv4和IPv6两种格式,我给你完整解析。
题目理解
输入:"172.16.254.1" → 输出:"IPv4"
输入:"2001:0db8:85a3:0:8A2E:0370:7334" → 输出:"IPv6"
输入:"256.256.256.256" → 输出:"Neither"(超出范围)
输入:"1.1.1.1." → 输出:"Neither"(多余分隔符)
方法一:按协议分隔判断(推荐)
python
def validIPAddress(queryIP: str) -> str:
"""
核心思路:根据分隔符判断协议类型,分别验证
时间复杂度:O(n),只遍历一次
空间复杂度:O(1)
"""
# ---------- IPv4 验证 ----------
def isIPv4(s: str) -> bool:
"""
IPv4 规则:
1. 用 "." 分隔,必须恰好有4段
2. 每段必须是数字
3. 每段范围:0-255
4. 不能有前导零(除了 "0" 本身)
- "0" 合法
- "00" 不合法
- "01" 不合法
- "255" 合法
"""
# 检查段数
parts = s.split('.')
if len(parts) != 4:
return False
for part in parts:
# 每段不能为空
if not part:
return False
# 每段必须全是数字
if not part.isdigit():
return False
# 不能有前导零:len("01") > 1
# "0" 本身长度为1,可以;"00" 长度>1,不行
if len(part) > 1 and part[0] == '0':
return False
# 数值范围检查
num = int(part)
if num < 0 or num > 255:
return False
return True
# ---------- IPv6 验证 ----------
def isIPv6(s: str) -> bool:
"""
IPv6 规则:
1. 用 ":" 分隔,必须恰好有8段
2. 每段必须是1-4个十六进制字符
3. 十六进制字符:0-9, a-f, A-F
简化实现:直接尝试 int(..., 16) 转换
"""
# 检查段数
parts = s.split(':')
if len(parts) != 8:
return False
# 十六进制字符集
hex_chars = set("0123456789abcdefABCDEF")
for part in parts:
# 每段不能为空
if not part:
return False
# 每段长度1-4
if len(part) > 4 or len(part) < 1:
return False
# 每段必须是十六进制字符
for c in part:
if c not in hex_chars:
return False
return True
# ---------- 主逻辑 ----------
# 先判断分隔符类型,快速筛选
if '.' in queryIP and ':' not in queryIP:
# 可能是 IPv4(IPv4用".",IPv6用":")
return "IPv4" if isIPv4(queryIP) else "Neither"
elif ':' in queryIP and '.' not in queryIP:
# 可能是 IPv6
return "IPv6" if isIPv6(queryIP) else "Neither"
else:
# 混合分隔符或不包含分隔符 → 非法
return "Neither"
# 测试
test_cases = [
"172.16.254.1", # IPv4
"256.256.256.256", # Neither(超出范围)
"1.1.1.1.", # Neither(多余分隔符)
".1.1.1.1", # Neither(前导分隔符)
"192.168.1.1", # IPv4
"2001:0db8:85a3:0:8A2E:0370:7334", # IPv6
"2001:db8:85a3:0:8A2E:370:7334", # IPv6
"2001:0db8:85a3::8A2E:0370:7334", # Neither(含::)
"1.1.1.1.1", # Neither(IPv4段数不对)
"1:1:1:1:1:1:1:1::", # Neither
"1:1:1:1:1:1:1:1G", # Neither(含非十六进制)
]
for ip in test_cases:
print(f'"{ip}" → {validIPAddress(ip)}')
方法二:正则表达式(简洁但难理解)
python
import re
def validIPAddress_regex(queryIP: str) -> str:
"""
使用正则表达式验证
IPv4 正则:
- ^ 和 $:匹配整个字符串
- (?:...):非捕获组
- \d:数字
- (?!.*\.):负向前瞻,确保没有其他点
- (?!\d*$ ):确保不是纯数字结尾
IPv6 正则:
- [0-9a-fA-F]{1,4}:1-4个十六进制字符
- (?::[0-9a-fA-F]{1,4}){7}:7组冒号+4个十六进制
"""
# IPv4 正则:每段 1-3位数字,范围 0-255
ipv4_pattern = r'^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$'
# IPv6 正则:每段 1-4个十六进制字符
ipv6_pattern = r'^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$'
if re.match(ipv4_pattern, queryIP):
return "IPv4"
elif re.match(ipv6_pattern, queryIP):
return "IPv6"
else:
return "Neither"
# IPv4 正则解释
"""
分步解析:^((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$
每一段可能的匹配:
- 25[0-5]:250-255
- 2[0-4]\d:200-249(\d = 0-9)
- 1\d{2}:100-199
- [1-9]?\d:1-99(? 表示可选,所以也能匹配 0-9)
为什么这样设计?
- 避免匹配 "256"、"01" 等非法情况
- 用分支覆盖所有 0-255 的合法情况
"""
方法三:状态机(最严谨,展示算法能力)
python
def validIPAddress_fsm(queryIP: str) -> str:
"""
有限状态机解法
状态转移图:
┌─────────────────────────────────────────────────────────┐
│ │
│ ┌──────┐ "." ┌──────┐ "." ┌──────┐ "." ┌──────┐ "." ┌────────┐
│ --→│ S0 │──────────→│ S1 │──────────→│ S2 │──────────→│ S3 │──────────→│ END │
│ │ 数字 │ │ 数字 │ │ 数字 │ │ 数字 │ │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └────────┘
│ │ │ │ │ │
│ └──────────────────┴──────────────────┴──────────────────┴──────────────────┘
│ 检查合法性
│
│ ┌──────┐ ":" ┌──────┐ ":" ┌──────┐ ":" ┌──────┐ ":" ┌────────┐
└--→ │ H0 │──────────→│ H1 │──────────→│ H2 │──────────→│ H7 │──────────→│ HEND │
│ 16进 │ │ 16进 │ │ 16进 │ │ 16进 │ │ │
└──────┘ └──────┘ └──────┘ └──────┘ └────────┘
S系列:IPv4 状态(S0→S1→S2→S3→END = 4段)
H系列:IPv6 状态(H0→H1→...→H7→HEND = 8段)
"""
def check_ipv4(s: str) -> bool:
"""
状态机验证 IPv4
"""
n = len(s)
if n == 0:
return False
# 状态:当前段数字个数、当前段数值、段计数
digit_count = 0 # 当前段的字符数
current_num = 0 # 当前段的数值
segment_count = 0 # 已完成的段数
for i, c in enumerate(s):
if c == '.':
# 遇到分隔符:检查当前段是否合法
if digit_count == 0:
# 空段,如 "1..2.3"
return False
if current_num > 255:
return False
if digit_count > 1 and s[i - digit_count] == '0':
# 有前导零,如 "01"
return False
# 重置状态,准备下一段
digit_count = 0
current_num = 0
segment_count += 1
elif c.isdigit():
# 累加数值(类似 parseInt)
current_num = current_num * 10 + int(c)
digit_count += 1
# 提前剪枝:数值已超
if current_num > 255:
return False
else:
# 遇到非法字符
return False
# 处理最后一段
if digit_count == 0:
return False
if current_num > 255:
return False
if digit_count > 1 and s[-digit_count] == '0':
return False
# 必须是4段
return segment_count == 3
def check_ipv6(s: str) -> bool:
"""
状态机验证 IPv6
"""
n = len(s)
if n == 0:
return False
hex_chars = set("0123456789abcdefABCDEF")
char_count = 0 # 当前段字符数
segment_count = 0 # 已完成的段数
for i, c in enumerate(s):
if c == ':':
# 遇到分隔符:检查当前段是否合法
if char_count == 0:
return False
if char_count > 4:
return False
char_count = 0
segment_count += 1
elif c in hex_chars:
char_count += 1
if char_count > 4:
return False
else:
return False
# 处理最后一段
if char_count == 0 or char_count > 4:
return False
# 必须是8段
return segment_count == 7
# 先根据分隔符判断协议类型
if '.' in queryIP and ':' not in queryIP:
return "IPv4" if check_ipv4(queryIP) else "Neither"
elif ':' in queryIP and '.' not in queryIP:
return "IPv6" if check_ipv6(queryIP) else "Neither"
else:
return "Neither"
完整测试
python
if __name__ == "__main__":
test_cases = [
# IPv4 测试
("172.16.254.1", "IPv4"),
("192.168.1.1", "IPv4"),
("0.0.0.0", "IPv4"),
("255.255.255.255", "IPv4"),
("1.2.3.4", "IPv4"),
# IPv4 非法情况
("256.256.256.256", "Neither"), # 超出范围
("1.1.1.01", "Neither"), # 前导零
("1.1.1.", "Neither"), # 空段
(".1.1.1", "Neither"), # 前导分隔符
("1.1.1", "Neither"), # 段数不足
("1.1.1.1.1", "Neither"), # 段数过多
("1.1.1.1.", "Neither"), # 多余分隔符
("1.1.1.1.1.1", "Neither"), # 段数过多
("01.1.1.1", "Neither"), # 前导零
("1.2.3.4.5", "Neither"), # 段数过多
# IPv6 测试
("2001:0db8:85a3:0:8A2E:0370:7334", "IPv6"),
("2001:0db8:85a3:0000:0000:8A2E:0370:7334", "IPv6"),
("1:1:1:1:1:1:1:1", "IPv6"),
("1234:abcd:1234:abcd:1234:abcd:1234:abcd", "IPv6"),
("::1", "Neither"), # 简写形式不处理
# IPv6 非法情况
("1:1:1:1:1:1:1:1:", "Neither"), # 尾部冒号
("1:1:1:1:1:1:1:1G", "Neither"), # 含非法字符
("1:1:1:1:1:1:1:1:1", "Neither"), # 段数过多
("1:1:1:1:1:1:1", "Neither"), # 段数不足
("1:1:1:1:1:1:1", "Neither"), # 段数不足
# 混合情况
("1.1.1.1:1", "Neither"), # 混合分隔符
("::", "Neither"), # IPv6 简写
("", "Neither"), # 空字符串
]
print("=" * 60)
print("方法一:分隔符判断")
print("=" * 60)
for ip, expected in test_cases:
result = validIPAddress(ip)
status = "✓" if result == expected else "✗"
print(f'{status} "{ip:45}" → {result:8} (期望: {expected})')
print("\n" + "=" * 60)
print("方法二:正则表达式")
print("=" * 60)
for ip, expected in test_cases:
result = validIPAddress_regex(ip)
status = "✓" if result == expected else "✗"
print(f'{status} "{ip:45}" → {result:8} (期望: {expected})')
关键知识点图解
IPv4 验证要点
合法 IPv4 示例:"192.168.1.1"
检查清单:
┌─────────────────────────────────────────────────────┐
│ 1. 段数 = 4 ✓ │
│ 2. 每段 = 数字 ✓ │
│ 3. 每段范围 0-255 ✓ │
│ 4. 无前导零 ✓ │
│ - "0" ✓ │
│ - "00" ✗ │
│ - "01" ✗ │
│ - "192" ✓ │
└─────────────────────────────────────────────────────┘
常见错误:
┌────────────────┬────────────────────┐
│ "256.1.1.1" │ 数值超 255 │
│ "1.01.1.1" │ 前导零 │
│ "1.1..1.1" │ 空段 │
│ "1.1.1" │ 段数不足 │
│ "1.1.1.1.1" │ 段数过多 │
└────────────────┴────────────────────┘
IPv6 验证要点
合法 IPv6 示例:"2001:0db8:85a3:0000:8A2E:0370:7334"
检查清单:
┌─────────────────────────────────────────────────────┐
│ 1. 段数 = 8 ✓ │
│ 2. 每段 = 1-4个十六进制字符 ✓ │
│ 3. 字符集:0-9, a-f, A-F ✓ │
└─────────────────────────────────────────────────────┘
十六进制字符:
┌──────┬────────────────────────────────┐
│ 数字 │ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 │
│ 小写 │ a, b, c, d, e, f │
│ 大写 │ A, B, C, D, E, F │
└──────┴────────────────────────────────┘
注意:本题不处理 IPv6 简写形式(如 "::1"、"2001::1")
面试建议
| 方法 | 推荐度 | 适用场景 |
|---|---|---|
| 分隔符判断 | ⭐⭐⭐⭐⭐ | 面试首选,逻辑清晰 |
| 正则表达式 | ⭐⭐⭐ | 简洁但需要记住正则 |
| 状态机 | ⭐⭐⭐ | 展示算法能力 |
回答要点:
- 先根据分隔符判断协议类型
- 重点讲清楚 IPv4 的前导零问题
- 提到可以用正则简化,但现场手写分隔判断更稳妥
