LeetCode 468. 验证IP地址
题目描述


题意分析
判断整个字符串是否符合题目规定的 IPv4 或 IPv6 格式,分别返回
"IPv4"、"IPv6";两种格式都不符合时返回"Neither"。这里只判断格式,不查询地址是否真实存在。IPv4 由点号分隔的四个十进制段组成,每段必须是
0到255的整数,除单独的0外不能有前导零。IPv6 由冒号分隔的八个十六进制段组成,每段包含一到四个十六进制字符,允许前导零和字母大小写。本题只接受完整的八段 IPv6,不接受双冒号压缩、IPv4 混合表示或空段。任意一个位置不满足规则,整串就不合法。
解法:分段后逐条校验规则
核心思路
[!blue]
两种格式都由固定数量的独立段组成,所以先检查整体分段,再逐段检查局部规则即可。代码根据分隔符分流:含点号就检查 IPv4,否则含冒号就检查 IPv6;两种分隔符混用时,分段后仍会留下不允许的字符,自然判错。
切分时必须保留空段。字符串以分隔符开头、结尾,或者出现连续分隔符,都意味着至少有一段为空。Java 的默认
split会丢弃尾部空段,要传入-1;点号在正则中有特殊意义,还需要转义。Go 的strings.Split按普通分隔字符串切分,并保留这些空段。IPv4 先限制每段非空且不超过三位,并拒绝多位数的前导零。再逐字符确认是 ASCII 数字,用
value = value * 10 + digit累加,最后判断是否超过255。段长限制保证累加不会溢出;字符规则、前导零规则和数值上限缺一不可。IPv6 只需检查八段,每段一到四位,字符都属于
0-9、a-f或A-F。四位以内的合法十六进制数本来就不会超过FFFF,因此无需转换成整数。所有段都通过各自规则,才返回对应地址类型。
解题步骤
- 根据是否包含点号、冒号选择校验分支;没有对应分隔符则返回
"Neither"。- 保留空段切分字符串,IPv4 必须恰好四段,IPv6 必须恰好八段。
- 对每个 IPv4 段,依次检查长度、前导零、十进制字符和累加后的数值上限。
- 对每个 IPv6 段,检查长度是否为一到四,以及每个字符是否为合法十六进制字符。
- 任一检查失败立即判错;只有全部段通过,才返回
"IPv4"或"IPv6"。
代码实现
class Solution {
public String validIPAddress(String queryIP) {
if (queryIP.indexOf('.') >= 0) {
return isIPv4(queryIP) ? "IPv4" : "Neither";
}
if (queryIP.indexOf(':') >= 0) {
return isIPv6(queryIP) ? "IPv6" : "Neither";
}
return "Neither";
}
private boolean isIPv4(String ip) {
// 保留尾部空段,才能拒绝末尾多出的点号。
String[] parts = ip.split("\\.", -1);
if (parts.length != 4) {
return false;
}
for (String part : parts) {
// 先限制段长和前导零,再逐字符检查十进制数。
if (part.isEmpty()
|| part.length() > 3
|| (part.length() > 1 && part.charAt(0) == '0')) {
return false;
}
int value = 0;
for (int i = 0; i < part.length(); i++) {
char ch = part.charAt(i);
if (ch < '0' || ch > '9') {
return false;
}
value = value * 10 + ch - '0';
}
if (value > 255) {
return false;
}
}
return true;
}
private boolean isIPv6(String ip) {
String[] parts = ip.split(":", -1);
if (parts.length != 8) {
return false;
}
for (String part : parts) {
if (part.isEmpty() || part.length() > 4) {
return false;
}
for (int i = 0; i < part.length(); i++) {
if (!isHexDigit(part.charAt(i))) {
return false;
}
}
}
return true;
}
private boolean isHexDigit(char ch) {
return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F';
}
}
import "strings"
func validIPAddress(queryIP string) string {
if strings.Contains(queryIP, ".") {
if isIPv4(queryIP) {
return "IPv4"
}
return "Neither"
}
if strings.Contains(queryIP, ":") {
if isIPv6(queryIP) {
return "IPv6"
}
return "Neither"
}
return "Neither"
}
func isIPv4(ip string) bool {
// 切分保留空段,首尾或连续分隔符都会在规则检查中被拒绝。
parts := strings.Split(ip, ".")
if len(parts) != 4 {
return false
}
for _, part := range parts {
// 先限制段长和前导零,再逐字符检查十进制数。
if len(part) == 0 || len(part) > 3 || len(part) > 1 && part[0] == '0' {
return false
}
value := 0
for i := 0; i < len(part); i++ {
if part[i] < '0' || part[i] > '9' {
return false
}
value = value*10 + int(part[i]-'0')
}
if value > 255 {
return false
}
}
return true
}
func isIPv6(ip string) bool {
parts := strings.Split(ip, ":")
if len(parts) != 8 {
return false
}
for _, part := range parts {
if len(part) == 0 || len(part) > 4 {
return false
}
for i := 0; i < len(part); i++ {
if !isHexDigit(part[i]) {
return false
}
}
}
return true
}
func isHexDigit(ch byte) bool {
return ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'f' || ch >= 'A' && ch <= 'F'
}
复杂度分析
- 时间复杂度:$O(n)$,
n为字符串长度。分隔符查找、切分和逐段校验各扫描至多线性数量的字符。- 空间复杂度:$O(n)$,切分结果需要保存段数组;对于包含大量分隔符的非法输入,段数也可能随输入长度增长。
关键点总结
[!green]
- 先确认段数和段长,再读取字符或转换数值,避免空段访问和遗漏格式限制。
- IPv4 同时约束字符、前导零和数值;IPv6 的长度与字符约束已经限制了数值范围。
- 保留空段是校验整个字符串的必要条件,不能让多余分隔符在切分时消失。
- 字符串能够转换成数值,不等于它符合题目规定的地址格式。
易错点总结
[!yellow]
- Java 切分时没有转义点号,或者省略
-1丢掉尾部空段,都会破坏对原始格式的判断。- 只检查 IPv4 的数值范围,没有单独检查多位数前导零,会把不合法的写法当成合法数字。
- 在检查段是否为空之前就读取首字符,会在连续或末尾分隔符处发生越界。
- 直接套用通用地址解析器支持的 IPv6 压缩规则,会接受本题明确不采用的格式。
- 十六进制字符只检查小写,或者允许超过
f的字母,都会错误判断 IPv6。- 只要部分段合法就返回类型,无法排除末尾的非法内容;必须检查段数并遍历所有段。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 93. 复原 IP 地址 | 中等 | 原题生成IPv4分段,本题验证既有地址;每段范围、前导零和段数约束可复用。 |
| 65. 有效数字 | 困难 | 同样做完整字符串格式校验,必须区分合法前缀与整串合法。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!