468. 验证IP地址
题目描述
输入字符串 queryIP,判断它是哪一类地址:合法 IPv4 返回 "IPv4",合法 IPv6 返回 "IPv6",都不是则返回 "Neither"。
合法 IPv4 由 4 段组成,段之间用 . 连接。每段是 0 到 255 的十进制整数,不能为空,也不能有多余的前导零(单独的 0 可以,01、00 不行),只能含数字。
合法 IPv6 由 8 段组成,段之间用 : 连接。每段长 1 到 4 个字符,字符只能是数字或 a-f、A-F。IPv6 的段允许前导零,但不能为空,也不能超过 4 个字符。
输入:queryIP = "172.16.254.1" 输出:"IPv4"
解释:四段都在 0 到 255 之间且无前导零。
输入:queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334" 输出:"IPv6"
解释:八段都是 1 到 4 位的十六进制串。
输入:queryIP = "256.256.256.256" 输出:"Neither"
解释:每段都超过 255,也不是 IPv6 格式。
数据范围
queryIP只包含英文字母、数字以及字符'.'和':'。
解题思路:分割字符串逐段校验
先按分隔符个数分类:恰好 3 个 . 就按 IPv4 检查,恰好 7 个 : 就按 IPv6 检查,其他一律 Neither。IPv4 每段:非空、最多 3 位、全是数字、无前导零、不超过 255。IPv6 每段:长度 1–4、全是十六进制字符。任一段不合法立即返回 Neither。
为什么正确
当前段之前的所有段都已通过全部规则。
复杂度分析
时间 O(n);空间 O(n)(分割结果)。
易错点
空段(如 "1.0.1.")很容易被 split 忽略:Java 的 split 默认会丢掉末尾空串,要传 -1。前导零只对 IPv4 非法,IPv6 允许。还要先限制长度再转整数,避免超长数字溢出。
参考代码
Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。
class Solution:
def validIPAddress(self, queryIP: str) -> str:
if queryIP.count('.') == 3:
for p in queryIP.split('.'):
if not p or len(p) > 3 or not p.isdigit():
return 'Neither'
if (p[0] == '0' and len(p) > 1) or int(p) > 255:
return 'Neither'
return 'IPv4'
if queryIP.count(':') == 7:
for p in queryIP.split(':'):
if not 1 <= len(p) <= 4 or any(c not in '0123456789abcdefABCDEF' for c in p):
return 'Neither'
return 'IPv6'
return 'Neither'class Solution {
public String validIPAddress(String queryIP) {
if (queryIP.chars().filter(c -> c == '.').count() == 3) {
for (String p : queryIP.split("\\.", -1)) {
if (p.isEmpty() || p.length() > 3 || !p.chars().allMatch(c -> c >= '0' && c <= '9')) {
return "Neither";
}
if ((p.charAt(0) == '0' && p.length() > 1) || Integer.parseInt(p) > 255) {
return "Neither";
}
}
return "IPv4";
}
if (queryIP.chars().filter(c -> c == ':').count() == 7) {
for (String p : queryIP.split(":", -1)) {
if (p.isEmpty() || p.length() > 4 || !p.chars().allMatch(c -> "0123456789abcdefABCDEF".indexOf(c) >= 0)) {
return "Neither";
}
}
return "IPv6";
}
return "Neither";
}
}class Solution {
public:
vector<string> split468(const string& s, char d) {
vector<string> r;
string cur;
for (char c : s) {
if (c == d) { r.push_back(cur); cur.clear(); }
else cur += c;
}
r.push_back(cur);
return r;
}
string validIPAddress(string queryIP) {
if (count(queryIP.begin(), queryIP.end(), '.') == 3) {
for (const string& p : split468(queryIP, '.')) {
if (p.empty() || p.size() > 3 || !all_of(p.begin(), p.end(), ::isdigit)) {
return "Neither";
}
if ((p[0] == '0' && p.size() > 1) || stoi(p) > 255) {
return "Neither";
}
}
return "IPv4";
}
if (count(queryIP.begin(), queryIP.end(), ':') == 7) {
for (const string& p : split468(queryIP, ':')) {
if (p.empty() || p.size() > 4 || !all_of(p.begin(), p.end(), ::isxdigit)) {
return "Neither";
}
}
return "IPv6";
}
return "Neither";
}
};func validIPAddress(queryIP string) string {
if strings.Count(queryIP, ".") == 3 {
for _, p := range strings.Split(queryIP, ".") {
if len(p) == 0 || len(p) > 3 || strings.Trim(p, "0123456789") != "" {
return "Neither"
}
v, _ := strconv.Atoi(p)
if (p[0] == '0' && len(p) > 1) || v > 255 {
return "Neither"
}
}
return "IPv4"
}
if strings.Count(queryIP, ":") == 7 {
for _, p := range strings.Split(queryIP, ":") {
if len(p) == 0 || len(p) > 4 || strings.Trim(p, "0123456789abcdefABCDEF") != "" {
return "Neither"
}
}
return "IPv6"
}
return "Neither"
}var validIPAddress = function(queryIP) {
if (queryIP.split('.').length === 4) {
for (const p of queryIP.split('.')) {
if (!p || p.length > 3 || !/^[0-9]+$/.test(p)) {
return 'Neither';
}
if ((p[0] === '0' && p.length > 1) || +p > 255) {
return 'Neither';
}
}
return 'IPv4';
}
if (queryIP.split(':').length === 8) {
for (const p of queryIP.split(':')) {
if (!p || p.length > 4 || !/^[0-9a-fA-F]+$/.test(p)) {
return 'Neither';
}
}
return 'IPv6';
}
return 'Neither';
};function validIPAddress(queryIP: string): string {
if (queryIP.split('.').length === 4) {
for (const p of queryIP.split('.')) {
if (!p || p.length > 3 || !/^[0-9]+$/.test(p)) {
return 'Neither';
}
if ((p[0] === '0' && p.length > 1) || +p > 255) {
return 'Neither';
}
}
return 'IPv4';
}
if (queryIP.split(':').length === 8) {
for (const p of queryIP.split(':')) {
if (!p || p.length > 4 || !/^[0-9a-fA-F]+$/.test(p)) {
return 'Neither';
}
}
return 'IPv6';
}
return 'Neither';
}其他解法
逐段校验每条规则都清楚可见;正则把规则压缩成两个模式,简洁但可读性差。
解法 2:正则表达式 时间 O(n),空间 O(1)
把规则写成正则:IPv4 每段为 25[0-5]、2[0-4]x、1xx 或 [1-9]?x,可排除前导零和超过 255 的数,共 4 段用点连接。IPv6 为 8 组 1~4 位十六进制数,用冒号连接。先尝试整串匹配 IPv4,再匹配 IPv6,都不匹配就返回 Neither。
规则集中在一处,代码最短。正则不易读,难以调试,也不容易给出具体的错误位置。
import re
class Solution:
# 每段 0~255 且无前导零:25[0-5] | 2[0-4]x | 1xx | [1-9]?x
IPV4 = re.compile(r'((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])')
# 8 组 1~4 位十六进制数
IPV6 = re.compile(r'([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}')
def validIPAddress(self, queryIP: str) -> str:
if self.IPV4.fullmatch(queryIP):
return 'IPv4'
if self.IPV6.fullmatch(queryIP):
return 'IPv6'
return 'Neither'class Solution {
private static final java.util.regex.Pattern IPV4 = java.util.regex.Pattern.compile("((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])");
private static final java.util.regex.Pattern IPV6 = java.util.regex.Pattern.compile("([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}");
public String validIPAddress(String queryIP) {
if (IPV4.matcher(queryIP).matches()) return "IPv4";
if (IPV6.matcher(queryIP).matches()) return "IPv6";
return "Neither";
}
}#include <regex>
class Solution {
public:
string validIPAddress(string queryIP) {
static const regex ipv4("((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])");
static const regex ipv6("([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}");
if (regex_match(queryIP, ipv4)) return "IPv4";
if (regex_match(queryIP, ipv6)) return "IPv6";
return "Neither";
}
};var (
ipv4Re = regexp.MustCompile(`^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$`)
ipv6Re = regexp.MustCompile(`^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$`)
)
func validIPAddress(queryIP string) string {
if ipv4Re.MatchString(queryIP) {
return "IPv4"
}
if ipv6Re.MatchString(queryIP) {
return "IPv6"
}
return "Neither"
}var validIPAddress = function(queryIP) {
const ipv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
const ipv6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
if (ipv4.test(queryIP)) return 'IPv4';
if (ipv6.test(queryIP)) return 'IPv6';
return 'Neither';
};function validIPAddress(queryIP: string): string {
const ipv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
const ipv6 = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
if (ipv4.test(queryIP)) return 'IPv4';
if (ipv6.test(queryIP)) return 'IPv6';
return 'Neither';
}