当前位置: 首页 > backend >正文

LeetCode 468. 验证IP地址 - 详细解析

文章目录

  • LeetCode 468. 验证IP地址 - 详细解析
    • 题目描述
      • IPv4验证规则:
      • IPv6验证规则:
    • 最优Java解决方案(注释完整版)
    • 关键变量含义及代码技巧
      • 代码技巧详解
        • 1. 前导零检查的最佳实践
        • 2. IPv6为什么不能用Character.isDigit()
        • 3. 针对性注释设计
    • 算法核心思路
    • 可视化演示过程
      • 测试用例1:有效IPv4 - "172.16.254.1"(优化版演示)
      • 测试用例2:有效IPv6 - "2001:0db8:85a3:0:0:8A2E:0370:7334"(优化版演示)
      • 测试用例3:无效IPv4(前导零)- "192.168.01.1"(优化版演示)
      • 测试用例4:无效IPv4(超出范围)- "256.256.256.256"(优化版演示)
      • 测试用例5:无效IPv6(段过长)- "02001:0db8:85a3:0000:0000:8a2e:0370:7334"
      • 测试用例6:无效格式(非法字符)- "192.168@1.1"
    • 算法复杂度分析
    • 关键技术点详解
      • String的isEmpty() vs isBlank()方法对比
        • 详细对比示例:
        • 在IP验证中的应用:
        • 安全的null检查:
      • split() 方法的 limit 参数
    • 代码优化技巧总结
    • 代码优势对比
    • 常见陷阱

LeetCode 468. 验证IP地址 - 详细解析

题目描述

给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 “IPv4” ;如果是有效的 IPv6 地址,返回 “IPv6” ;如果不是上述类型的 IP 地址,返回 “Neither” 。

IPv4验证规则:

  • 格式:“x1.x2.x3.x4”
  • 0 <= xi <= 255
  • xi 不能包含前导零(除了"0"本身)

IPv6验证规则:

  • 格式:“x1:x2:x3:x4:x5:x6:x7:x8”
  • 1 <= xi.length <= 4
  • xi 是十六进制字符串(0-9, a-f, A-F)
  • 允许前导零

最优Java解决方案(注释完整版)

class Solution {public String validIPAddress(String queryIP) {if (queryIP.contains(".")) {return isValidIPV4(queryIP) ? "IPv4" : "Neither";} else if (queryIP.contains(":")) {return isValidIPV6(queryIP) ? "IPv6" : "Neither";} else return "Neither";}boolean isValidIPV4(String ip) {String[] parts = ip.split("\\.", -1);if (parts.length != 4) {return false;}for (String part : parts) {int n = part.length();if (n == 0 || n > 3) { // 针对""和2555的输入return false;}if (n > 1 && part.startsWith("0")) { // 针对10.01.1.1输入,前导零return false;}for (char c : part.toCharArray()) { // 针对非数字输入if (!Character.isDigit(c)) {return false;}}int num = Integer.parseInt(part); // 针对256.0.0.1输入if (!(num >= 0 && num <= 255)) {return false;}}return true;}boolean isValidIPV6(String ip) {String[] parts = ip.split(":", -1);if (parts.length != 8) {return false;}for (String part : parts) {int n = part.length();if (n == 0 || n > 4) { // 针对空串和11111输入return false;}for (char c : part.toCharArray()) {// 这里不能替换为Character.isDigit(c)if (!((c >= '0' && c <= '9') ||(c >= 'a' && c <= 'f') ||(c >= 'A' && c <= 'F'))) {return false;}}}return true;}
}

关键变量含义及代码技巧

变量名含义作用
queryIP输入的待验证IP字符串算法的输入参数
parts分割后的IP段数组存储按".“或”:"分割的各个部分
part单个IP段字符串用于验证每个独立的IP段
nIP段的长度int n = part.length() 提高代码可读性
numIPv4段的整数值用于检查IPv4段是否在0-255范围内
c字符变量用于逐字符检查是否符合格式要求

代码技巧详解

1. 前导零检查的最佳实践
if (n > 1 && part.startsWith("0")) { // 针对10.01.1.1输入,前导零return false;
}

为什么用 startsWith("0") 而不是 charAt(0) == '0'

  • 更语义化,表达"以0开头"的意图更清晰
  • startsWith() 内部已经做了边界检查,更安全
2. IPv6为什么不能用Character.isDigit()
// 这里不能替换为Character.isDigit(c)
if (!((c >= '0' && c <= '9') ||(c >= 'a' && c <= 'f') ||(c >= 'A' && c <= 'F'))) {return false;
}

原因分析:

  • Character.isDigit(c) 只检查数字字符(0-9)
  • IPv6需要十六进制字符:数字(0-9) + 字母(a-f, A-F)
  • 必须手动检查三个范围:数字、小写字母、大写字母
3. 针对性注释设计

代码中的注释都标明了具体要处理的边界情况:

  • // 针对""和2555的输入 → 长度检查
  • // 针对10.01.1.1输入,前导零 → 前导零检查
  • // 针对非数字输入 → 字符合法性检查
  • // 针对256.0.0.1输入 → 数值范围检查
  • // 针对空串和11111输入 → IPv6长度检查

算法核心思路

  1. 预判断:通过检查是否包含".“或”:"来初步判断IP类型
  2. 分割验证:将IP按分隔符分割成段,验证段数是否正确
  3. 逐段检查:对每个段进行格式和数值范围验证
  4. 字符级验证:确保每个字符都符合对应IP类型的要求

可视化演示过程

测试用例1:有效IPv4 - “172.16.254.1”(优化版演示)

步骤1:初步判断
queryIP = "172.16.254.1"
包含"." ✓ → 进入IPv4验证步骤2:分割并长度检查
parts = ["172", "16", "254", "1"]
parts.length = 4 ✓ → 段数正确,继续验证步骤3:逐段内联验证
part[0] = "172"
├── 长度检查: 3 ≤ 3 ✓
├── 前导零检查: 首字符'1' ≠ '0' ✓
├── 字符检查: '1','7','2' 全为数字 ✓
└── 数值检查: Integer.parseInt("172") = 172 ≤ 255 ✓part[1] = "16"
├── 长度检查: 2 ≤ 3 ✓
├── 前导零检查: 首字符'1' ≠ '0' ✓
├── 字符检查: '1','6' 全为数字 ✓
└── 数值检查: Integer.parseInt("16") = 16 ≤ 255 ✓part[2] = "254"
├── 长度检查: 3 ≤ 3 ✓
├── 前导零检查: 首字符'2' ≠ '0' ✓
├── 字符检查: '2','5','4' 全为数字 ✓
└── 数值检查: Integer.parseInt("254") = 254 ≤ 255 ✓part[3] = "1"
├── 长度检查: 1 ≤ 3 ✓
├── 前导零检查: 长度=1,无需检查 ✓
├── 字符检查: '1' 为数字 ✓
└── 数值检查: Integer.parseInt("1") = 1 ≤ 255 ✓结果:返回 "IPv4"

测试用例2:有效IPv6 - “2001:0db8:85a3:0:0:8A2E:0370:7334”(优化版演示)

步骤1:初步判断
queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334"
不包含"." 但包含":" ✓ → 进入IPv6验证步骤2:分割并长度检查
parts = ["2001", "0db8", "85a3", "0", "0", "8A2E", "0370", "7334"]
parts.length = 8 ✓ → 段数正确,继续验证步骤3:逐段内联验证
part[0] = "2001"
├── 长度检查: 4 ≤ 4 ✓
└── 内联十六进制检查:│ '2': '0'≤'2'≤'9' ✓│ '0': '0'≤'0'≤'9' ✓│ '0': '0'≤'0'≤'9' ✓│ '1': '0'≤'1'≤'9' ✓part[1] = "0db8"
├── 长度检查: 4 ≤ 4 ✓
└── 内联十六进制检查:│ '0': '0'≤'0'≤'9' ✓│ 'd': 'a'≤'d'≤'f' ✓│ 'b': 'a'≤'b'≤'f' ✓│ '8': '0'≤'8'≤'9' ✓part[5] = "8A2E"
├── 长度检查: 4 ≤ 4 ✓
└── 内联十六进制检查:│ '8': '0'≤'8'≤'9' ✓│ 'A': 'A'≤'A'≤'F' ✓│ '2': '0'≤'2'≤'9' ✓│ 'E': 'A'≤'E'≤'F' ✓... (其他段类似验证) ...结果:返回 "IPv6"

测试用例3:无效IPv4(前导零)- “192.168.01.1”(优化版演示)

步骤1:初步判断
queryIP = "192.168.01.1"
包含"." ✓ → 进入IPv4验证步骤2:分割并长度检查
parts = ["192", "168", "01", "1"]
parts.length = 4 ✓ → 段数正确,继续验证步骤3:逐段内联验证
part[0] = "192" ✓ (验证通过)
part[1] = "168" ✓ (验证通过)
part[2] = "01"
├── 长度检查: 2 ≤ 3 ✓
├── 前导零检查: 长度>1 且 首字符='0' ✗
└── 立即返回false,无需进行后续计算结果:返回 "Neither"

测试用例4:无效IPv4(超出范围)- “256.256.256.256”(优化版演示)

步骤1:初步判断
queryIP = "256.256.256.256"
包含"." ✓ → 进入IPv4验证步骤2:分割并长度检查
parts = ["256", "256", "256", "256"]
parts.length = 4 ✓ → 段数正确,继续验证步骤3:逐段内联验证
part[0] = "256"
├── 长度检查: 3 ≤ 3 ✓
├── 前导零检查: 首字符'2' ≠ '0' ✓
├── 字符检查: '2','5','6' 全为数字 ✓
└── 数值检查: Integer.parseInt("256") = 256 > 255 ✗结果:返回 "Neither"

测试用例5:无效IPv6(段过长)- “02001:0db8:85a3:0000:0000:8a2e:0370:7334”

步骤1:初步判断
queryIP = "02001:0db8:85a3:0000:0000:8a2e:0370:7334"
不包含"." 但包含":" ✓ → 可能是IPv6步骤2:分割IP
parts = ["02001", "0db8", "85a3", "0000", "0000", "8a2e", "0370", "7334"]
parts.length = 8 ✓ → 段数正确步骤3:逐段验证
part[0] = "02001"
├── 长度检查: 5 > 4 ✗
└── 验证失败!结果:返回 "Neither"

测试用例6:无效格式(非法字符)- “192.168@1.1”

步骤1:初步判断
queryIP = "192.168@1.1"
包含"." ✓ → 可能是IPv4步骤2:分割IP
parts = ["192", "168@1", "1"]
parts.length = 3 ≠ 4 ✗结果:返回 "Neither"

算法复杂度分析

  • 时间复杂度:O(n),其中n是字符串长度。需要遍历整个字符串进行分割和验证
  • 空间复杂度:O(1),除了存储分割后的段数组,不需要额外空间

关键技术点详解

String的isEmpty() vs isBlank()方法对比

在IP验证中,我们经常需要检查字符串是否为空,了解这两个方法的区别很重要:

方法作用Java版本检查内容
isEmpty()检查长度是否为0Java 6+只检查 length() == 0
isBlank()检查是否为空或只有空白字符Java 11+检查 isEmpty() || 全为空白字符
详细对比示例:
String str1 = "";           // 空字符串
String str2 = " ";          // 一个空格
String str3 = "  \t\n ";    // 多个空白字符(空格、制表符、换行符)
String str4 = " a ";        // 包含非空白字符
String str5 = null;         // null值// isEmpty() 结果:
str1.isEmpty()true(长度为0)
str2.isEmpty()false(长度为1)
str3.isEmpty()false(长度为4)
str4.isEmpty()false(长度为3)
// str5.isEmpty() → NullPointerException!// isBlank() 结果 (Java 11+):
str1.isBlank()true(长度为0)
str2.isBlank()true(只有空白字符)
str3.isBlank()true(只有空白字符)
str4.isBlank()false(包含非空白字符)
// str5.isBlank() → NullPointerException!
在IP验证中的应用:
// 检查IP段是否为空的不同方式
private boolean isValidIPv4Part(String part) {// 方式1:直接检查长度(推荐)if (part.length() == 0) return false;// 方式2:使用isEmpty()(等效)if (part.isEmpty()) return false;// 方式3:使用isBlank()(Java 11+,更严格)if (part.isBlank()) return false; // 会拒绝 " " 这样的空格段// ... 其他验证逻辑
}
安全的null检查:
// 推荐的安全检查方式
public static boolean isNullOrEmpty(String str) {return str == null || str.isEmpty();
}public static boolean isNullOrBlank(String str) {return str == null || str.isBlank(); // Java 11+
}

split() 方法的 limit 参数

String[] parts = ip.split("\\.", -1);

split(regex, limit) 中的 limit 参数控制分割行为:

limit值行为示例
limit > 0最多分割成limit个部分"a.b.c".split("\\.", 2)["a", "b.c"]
limit = 0默认行为,移除尾部空字符串"a.b.".split("\\.")["a", "b"]
limit < 0保留所有空字符串(包括尾部)"a.b.".split("\\.", -1)["a", "b", ""]

为什么IP验证必须用 -1

// 测试案例对比
String ip1 = "192.168.1.";     // 末尾多点号
String ip2 = "192..168.1";     // 连续点号// 不使用-1 (默认行为)
ip1.split("\\.")["192", "168", "1"]     // 长度=3,错误!应该是4段
ip2.split("\\.")["192", "", "168", "1"] // 长度=4,但漏掉了空段检测// 使用-1 (正确行为)  
ip1.split("\\.", -1)["192", "168", "1", ""] // 长度=4,能检测到末尾空段
ip2.split("\\.", -1)["192", "", "168", "1"] // 长度=4,能检测到中间空段

代码优化技巧总结

  1. 关键的 split(-1):确保捕获所有边界情况,包括末尾和中间的空段
  2. 长度预判断:直接检查分割后数组长度,不符合直接返回false
  3. 语义化变量命名:使用 int n = part.length() 提高代码可读性
  4. 语义化方法调用:使用 part.startsWith("0") 替代 part.charAt(0) == '0'
  5. 详细的针对性注释:每个检查都注明具体处理的边界情况
  6. 精确的字符范围检查:IPv6手动检查三个字符范围,不使用Character.isDigit()
  7. 清晰的条件表达式:使用 !(num >= 0 && num <= 255) 明确表达范围检查

代码优势对比

优化项传统写法当前实现优势说明
变量命名part.length() 重复调用int n = part.length()提高可读性,减少重复计算
前导零检查part.charAt(0) == '0'part.startsWith("0")语义更清晰,表达意图更直接
注释设计简单功能注释针对性边界情况注释明确每个检查要处理的具体问题
字符范围检查使用库函数或正则手动三范围检查IPv6需求下更精确,避免误判
条件表达式num < 0 || num > 255!(num >= 0 && num <= 255)逻辑更直观,表达"不在范围内"
长度预检查在循环中检查预先检查数组长度提早退出,避免无效处理

常见陷阱

  1. 忘记检查前导零(IPv4)
  2. 未正确处理空段(如连续分隔符)
  3. 字符范围检查不全面(IPv6的大小写字母)
  4. 数值范围检查遗漏边界值
http://www.xdnf.cn/news/20359.html

相关文章:

  • 嵌入式学习笔记--Linux系统编程阶段--DAY07进程间通信--存储映射和共享内存
  • 区块链技术
  • 如何减少微型导轨表面破损情况?
  • JWT概念及使用详解
  • Dart语言基础 关键字 var与dynamic
  • 整车无线布置的综述
  • 【完整源码+数据集+部署教程】室内场景分割系统源码和数据集:改进yolo11-DWR
  • 算法题(200):最大子段和(动态规划)
  • 责任链框架 03:处理器实现
  • 《Science》神经炎症综述思路套用:从机制到跨领域研究范式
  • Python实现生成矩形框、三角形框、六边形框和圆环点云
  • 自动拆箱和装箱的原理与作用
  • HMI(人机界面)
  • 【基础-单选】UIAbility实例创建完成时触发的回调
  • HTML 列表类型
  • 5-8单元格区域与VS数组应用(实例:提取满足条件的数据)
  • Qt多线程编程学习
  • EG2103 SOP-8 内置600V功率MOS管 栅极驱动芯片
  • I/O 多路复用 (I/O Multiplexing)
  • 四个关于云属性的四个卫星数据集的介绍
  • 基于Spring Boot + Vue3的办公用品申领管理系统
  • 部署AIRI
  • lesson55:CSS导航组件全攻略:从基础导航条到动态三级菜单与伸缩菜单实现
  • 02.继承MonoBehaviour的单例模式基类
  • Python快速入门专业版(七):整数与浮点数:Python数值类型的运算与精度问题(附解决方案)
  • 项目中的一些比较实用的自定义控件
  • Python文件打包为EXE的工具v1.0
  • 《AI大模型应知应会100篇》第67篇 Web应用与大模型集成开发实践——1小时打造国产大模型智能客服系统
  • MySQL问题5
  • github上传步骤