文章目錄
- 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段 |
n | IP段的長度 | int n = part.length() 提高代碼可讀性 |
num | IPv4段的整數值 | 用于檢查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長度檢查
算法核心思路
- 預判斷:通過檢查是否包含".“或”:"來初步判斷IP類型
- 分割驗證:將IP按分隔符分割成段,驗證段數是否正確
- 逐段檢查:對每個段進行格式和數值范圍驗證
- 字符級驗證:確保每個字符都符合對應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() | 檢查長度是否為0 | Java 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,能檢測到中間空段
代碼優化技巧總結
- 關鍵的 split(-1):確保捕獲所有邊界情況,包括末尾和中間的空段
- 長度預判斷:直接檢查分割后數組長度,不符合直接返回false
- 語義化變量命名:使用
int n = part.length()
提高代碼可讀性 - 語義化方法調用:使用
part.startsWith("0")
替代part.charAt(0) == '0'
- 詳細的針對性注釋:每個檢查都注明具體處理的邊界情況
- 精確的字符范圍檢查:IPv6手動檢查三個字符范圍,不使用
Character.isDigit()
- 清晰的條件表達式:使用
!(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) | 邏輯更直觀,表達"不在范圍內" |
長度預檢查 | 在循環中檢查 | 預先檢查數組長度 | 提早退出,避免無效處理 |
常見陷阱
- 忘記檢查前導零(IPv4)
- 未正確處理空段(如連續分隔符)
- 字符范圍檢查不全面(IPv6的大小寫字母)
- 數值范圍檢查遺漏邊界值