Facebook Pixel

468. Validate IP Address

MediumString
Leetcode Link

Problem Description

You need to determine if a given string queryIP represents a valid IP address. The function should return:

  • "IPv4" if it's a valid IPv4 address
  • "IPv6" if it's a valid IPv6 address
  • "Neither" if it's not a valid IP address of either type

IPv4 Address Rules:

  • Must have exactly 4 segments separated by dots (.)
  • Format: "x1.x2.x3.x4" where each xi is a decimal number
  • Each segment must be between 0 and 255 (inclusive)
  • Leading zeros are NOT allowed (except for the number 0 itself)
  • Examples:
    • Valid: "192.168.1.1", "192.168.1.0"
    • Invalid: "192.168.01.1" (leading zero), "192.168.1.00" (leading zeros), "192.168@1.1" (invalid character)

IPv6 Address Rules:

  • Must have exactly 8 segments separated by colons (:)
  • Format: "x1:x2:x3:x4:x5:x6:x7:x8" where each xi is a hexadecimal string
  • Each segment must have 1 to 4 characters
  • Valid characters are digits (0-9), lowercase letters (a-f), and uppercase letters (A-F)
  • Leading zeros ARE allowed in IPv6
  • Examples:
    • Valid: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3:0:0:8A2E:0370:7334"
    • Invalid: "2001:0db8:85a3::8A2E:037j:7334" (contains :: and invalid character j), "02001:0db8:85a3:0000:0000:8a2e:0370:7334" (first segment has 5 characters)

The solution involves creating two helper functions to validate IPv4 and IPv6 formats separately, checking all the specific rules for each format, and returning the appropriate result based on which validation passes.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that IPv4 and IPv6 have completely different formats and validation rules, so we should handle them separately rather than trying to create a unified validation approach.

We can determine which type to check by looking at the separators in the string:

  • IPv4 uses dots (.) as separators
  • IPv6 uses colons (:) as separators

Since a valid IP address can only be one type or neither, we can use a straightforward approach:

  1. Try to validate as IPv4
  2. If that fails, try to validate as IPv6
  3. If both fail, return "Neither"

For IPv4 validation, we need to check:

  • Split by . should give exactly 4 parts
  • Each part must be a valid decimal number (0-255)
  • No leading zeros allowed (except "0" itself)

The tricky part with IPv4 is the leading zero rule. A string like "01" is invalid, but "0" is valid. We can check this by seeing if the length is greater than 1 and starts with "0".

For IPv6 validation, we need to check:

  • Split by : should give exactly 8 parts
  • Each part must be 1-4 characters long
  • Each character must be a valid hexadecimal digit (0-9, a-f, A-F)

IPv6 is actually simpler in some ways because leading zeros are allowed, so we don't need that extra check. We just need to validate that each segment contains only valid hexadecimal characters and has the right length.

By breaking down the problem into two separate validation functions, we make the code cleaner and easier to understand. Each function focuses on one specific format's rules, making it straightforward to implement and debug.

Solution Approach

We implement two separate validation functions: is_ipv4 and is_ipv6, then check the input string against each one.

IPv4 Validation (is_ipv4 function):

  1. Split the string: Use s.split(".") to divide the string into segments. If we don't get exactly 4 segments, return False immediately.

  2. Validate each segment:

    • Check for leading zeros: If len(t) > 1 and t[0] == "0", the segment has invalid leading zeros (like "01" or "001"), so return False.
    • Check if it's a valid number: Use t.isdigit() to ensure all characters are digits.
    • Check the range: Convert to integer and verify 0 <= int(t) <= 255.
  3. If all segments pass validation, return True.

IPv6 Validation (is_ipv6 function):

  1. Split the string: Use s.split(":") to divide the string into segments. If we don't get exactly 8 segments, return False immediately.

  2. Validate each segment:

    • Check length: Each segment must have 1 <= len(t) <= 4 characters.
    • Check characters: Use all(c in "0123456789abcdefABCDEF" for c in t) to ensure every character is a valid hexadecimal digit.
  3. If all segments pass validation, return True.

Main Function Logic:

if is_ipv4(queryIP):
    return "IPv4"
if is_ipv6(queryIP):
    return "IPv6"
return "Neither"

The order of checking doesn't matter since a valid IP can only be one type. We first check IPv4, then IPv6, and if both fail, we return "Neither".

Key Implementation Details:

  • The isdigit() method is perfect for IPv4 validation as it returns True only if all characters are decimal digits.
  • For IPv6, we explicitly check against the allowed character set "0123456789abcdefABCDEF" since hexadecimal includes letters.
  • The leading zero check for IPv4 is done before the numeric validation to avoid issues with strings like "00" which would pass the range check but are invalid.
  • Empty segments would naturally fail our validations (empty string won't pass isdigit() for IPv4, and would have length 0 for IPv6).

This approach is straightforward and handles all edge cases by explicitly checking each validation rule defined in the problem.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with two examples to see how the validation works.

Example 1: queryIP = "172.16.254.1"

  1. First, we try is_ipv4("172.16.254.1"):

    • Split by .: ["172", "16", "254", "1"] - we get exactly 4 segments ✓
    • Check segment "172":
      • Length > 1 and starts with '0'? No (starts with '1') ✓
      • All digits? Yes ✓
      • In range [0, 255]? Yes (172 is valid) ✓
    • Check segment "16":
      • Length > 1 and starts with '0'? No ✓
      • All digits? Yes ✓
      • In range [0, 255]? Yes ✓
    • Check segment "254":
      • Length > 1 and starts with '0'? No ✓
      • All digits? Yes ✓
      • In range [0, 255]? Yes ✓
    • Check segment "1":
      • Length > 1 and starts with '0'? No (length = 1) ✓
      • All digits? Yes ✓
      • In range [0, 255]? Yes ✓
    • All segments valid, return True
  2. Since is_ipv4 returned True, we return "IPv4"

Example 2: queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334"

  1. First, we try is_ipv4("2001:0db8:85a3:0:0:8A2E:0370:7334"):

    • Split by .: ["2001:0db8:85a3:0:0:8A2E:0370:7334"] - only 1 segment, not 4 ✗
    • Return False
  2. Next, we try is_ipv6("2001:0db8:85a3:0:0:8A2E:0370:7334"):

    • Split by :: ["2001", "0db8", "85a3", "0", "0", "8A2E", "0370", "7334"] - exactly 8 segments ✓
    • Check segment "2001":
      • Length between 1-4? Yes (length = 4) ✓
      • All characters in "0123456789abcdefABCDEF"? Yes ✓
    • Check segment "0db8":
      • Length between 1-4? Yes (length = 4) ✓
      • All characters valid hex? Yes ✓
    • Check segment "85a3":
      • Length between 1-4? Yes ✓
      • All characters valid hex? Yes ✓
    • Check segments "0", "0": Both have length 1 and valid hex ✓
    • Check segment "8A2E":
      • Length between 1-4? Yes ✓
      • All characters valid hex? Yes (uppercase A and E are valid) ✓
    • Check segments "0370", "7334": Both valid ✓
    • All segments valid, return True
  3. Since is_ipv6 returned True, we return "IPv6"

Example 3: queryIP = "192.168.01.1"

  1. First, we try is_ipv4("192.168.01.1"):

    • Split by .: ["192", "168", "01", "1"] - exactly 4 segments ✓
    • Check segments "192", "168": Both valid ✓
    • Check segment "01":
      • Length > 1 and starts with '0'? Yes (length = 2, starts with '0') ✗
      • This is a leading zero violation, return False
  2. Next, we try is_ipv6("192.168.01.1"):

    • Split by :: ["192.168.01.1"] - only 1 segment, not 8 ✗
    • Return False
  3. Both validations failed, so we return "Neither"

Solution Implementation

1class Solution:
2    def validIPAddress(self, queryIP: str) -> str:
3        """
4        Validates whether a given string is a valid IPv4 or IPv6 address.
5      
6        Args:
7            queryIP: String to validate as an IP address
8          
9        Returns:
10            "IPv4" if valid IPv4, "IPv6" if valid IPv6, "Neither" otherwise
11        """
12      
13        def is_ipv4(ip_string: str) -> bool:
14            """
15            Checks if the string is a valid IPv4 address.
16            IPv4 format: X.X.X.X where X is a number from 0 to 255
17            """
18            # Split by dots - IPv4 should have exactly 4 segments
19            segments = ip_string.split(".")
20            if len(segments) != 4:
21                return False
22          
23            for segment in segments:
24                # Check for leading zeros (not allowed except for "0" itself)
25                if len(segment) > 1 and segment[0] == "0":
26                    return False
27              
28                # Check if segment contains only digits and is in valid range
29                if not segment.isdigit() or not 0 <= int(segment) <= 255:
30                    return False
31          
32            return True
33      
34        def is_ipv6(ip_string: str) -> bool:
35            """
36            Checks if the string is a valid IPv6 address.
37            IPv6 format: X:X:X:X:X:X:X:X where X is a 1-4 digit hexadecimal
38            """
39            # Split by colons - IPv6 should have exactly 8 segments
40            segments = ip_string.split(":")
41            if len(segments) != 8:
42                return False
43          
44            for segment in segments:
45                # Each segment should be 1-4 characters long
46                if not 1 <= len(segment) <= 4:
47                    return False
48              
49                # Check if all characters are valid hexadecimal digits
50                if not all(char in "0123456789abcdefABCDEF" for char in segment):
51                    return False
52          
53            return True
54      
55        # Check which type of IP address it is
56        if is_ipv4(queryIP):
57            return "IPv4"
58        if is_ipv6(queryIP):
59            return "IPv6"
60        return "Neither"
61
1class Solution {
2    /**
3     * Validates whether a given string is a valid IPv4 or IPv6 address
4     * @param queryIP the input string to validate
5     * @return "IPv4" if valid IPv4, "IPv6" if valid IPv6, "Neither" otherwise
6     */
7    public String validIPAddress(String queryIP) {
8        if (isIPv4(queryIP)) {
9            return "IPv4";
10        }
11        if (isIPv6(queryIP)) {
12            return "IPv6";
13        }
14        return "Neither";
15    }
16
17    /**
18     * Checks if the given string is a valid IPv4 address
19     * IPv4 format: x1.x2.x3.x4 where 0 <= xi <= 255 and xi cannot have leading zeros
20     * @param ipString the string to validate
21     * @return true if valid IPv4, false otherwise
22     */
23    private boolean isIPv4(String ipString) {
24        // IPv4 cannot end with a dot
25        if (ipString.endsWith(".")) {
26            return false;
27        }
28      
29        // Split by dot separator
30        String[] segments = ipString.split("\\.");
31      
32        // IPv4 must have exactly 4 segments
33        if (segments.length != 4) {
34            return false;
35        }
36      
37        // Validate each segment
38        for (String segment : segments) {
39            // Check for empty segment or leading zeros (except single "0")
40            if (segment.length() == 0 || (segment.length() > 1 && segment.charAt(0) == '0')) {
41                return false;
42            }
43          
44            // Convert to integer and validate range [0, 255]
45            int value = convertToInteger(segment);
46            if (value < 0 || value > 255) {
47                return false;
48            }
49        }
50      
51        return true;
52    }
53
54    /**
55     * Checks if the given string is a valid IPv6 address
56     * IPv6 format: x1:x2:x3:x4:x5:x6:x7:x8 where xi is a hexadecimal string (1-4 digits)
57     * @param ipString the string to validate
58     * @return true if valid IPv6, false otherwise
59     */
60    private boolean isIPv6(String ipString) {
61        // IPv6 cannot end with a colon
62        if (ipString.endsWith(":")) {
63            return false;
64        }
65      
66        // Split by colon separator
67        String[] segments = ipString.split(":");
68      
69        // IPv6 must have exactly 8 segments
70        if (segments.length != 8) {
71            return false;
72        }
73      
74        // Validate each segment
75        for (String segment : segments) {
76            // Each segment must be 1-4 characters long
77            if (segment.length() < 1 || segment.length() > 4) {
78                return false;
79            }
80          
81            // Validate each character is a valid hexadecimal digit
82            for (char character : segment.toCharArray()) {
83                if (!Character.isDigit(character) && 
84                    !"0123456789abcdefABCDEF".contains(String.valueOf(character))) {
85                    return false;
86                }
87            }
88        }
89      
90        return true;
91    }
92
93    /**
94     * Converts a string to integer, returns -1 if string contains non-digit characters
95     * @param numberString the string to convert
96     * @return the integer value or -1 if invalid
97     */
98    private int convertToInteger(String numberString) {
99        int result = 0;
100      
101        for (char character : numberString.toCharArray()) {
102            // Return -1 if non-digit character found
103            if (!Character.isDigit(character)) {
104                return -1;
105            }
106          
107            // Build the integer value
108            result = result * 10 + (character - '0');
109          
110            // Early return if value exceeds 255
111            if (result > 255) {
112                return result;
113            }
114        }
115      
116        return result;
117    }
118}
119
1class Solution {
2public:
3    string validIPAddress(string queryIP) {
4        // Check if the input is a valid IPv4 address
5        if (isIPv4(queryIP)) {
6            return "IPv4";
7        }
8        // Check if the input is a valid IPv6 address
9        if (isIPv6(queryIP)) {
10            return "IPv6";
11        }
12        // Neither IPv4 nor IPv6
13        return "Neither";
14    }
15
16private:
17    bool isIPv4(const string& ipString) {
18        // IPv4 cannot be empty or end with a dot
19        if (ipString.empty() || ipString.back() == '.') {
20            return false;
21        }
22      
23        // Split the string by dots
24        vector<string> segments = split(ipString, '.');
25      
26        // IPv4 must have exactly 4 segments
27        if (segments.size() != 4) {
28            return false;
29        }
30      
31        // Validate each segment
32        for (const string& segment : segments) {
33            // Segment cannot be empty or have leading zeros (except for "0" itself)
34            if (segment.empty() || (segment.size() > 1 && segment[0] == '0')) {
35                return false;
36            }
37          
38            // Convert segment to integer and validate range [0, 255]
39            int value = convertToInt(segment);
40            if (value < 0 || value > 255) {
41                return false;
42            }
43        }
44      
45        return true;
46    }
47
48    bool isIPv6(const string& ipString) {
49        // IPv6 cannot be empty or end with a colon
50        if (ipString.empty() || ipString.back() == ':') {
51            return false;
52        }
53      
54        // Split the string by colons
55        vector<string> segments = split(ipString, ':');
56      
57        // IPv6 must have exactly 8 segments
58        if (segments.size() != 8) {
59            return false;
60        }
61      
62        // Validate each segment
63        for (const string& segment : segments) {
64            // Each segment must be 1-4 characters long
65            if (segment.size() < 1 || segment.size() > 4) {
66                return false;
67            }
68          
69            // Each character must be a valid hexadecimal digit
70            for (char ch : segment) {
71                if (!isxdigit(ch)) {
72                    return false;
73                }
74            }
75        }
76      
77        return true;
78    }
79
80    int convertToInt(const string& str) {
81        int result = 0;
82      
83        // Convert string to integer, checking for non-digit characters
84        for (char ch : str) {
85            if (!isdigit(ch)) {
86                return -1;  // Invalid character found
87            }
88          
89            result = result * 10 + (ch - '0');
90          
91            // Early return if value exceeds 255
92            if (result > 255) {
93                return result;
94            }
95        }
96      
97        return result;
98    }
99
100    vector<string> split(const string& str, char delimiter) {
101        vector<string> tokens;
102        string token;
103        istringstream stringStream(str);
104      
105        // Split the string by the delimiter using getline
106        while (getline(stringStream, token, delimiter)) {
107            tokens.push_back(token);
108        }
109      
110        return tokens;
111    }
112};
113
1/**
2 * Validates if a given string is a valid IPv4 or IPv6 address
3 * @param queryIP - The IP address string to validate
4 * @returns 'IPv4' if valid IPv4, 'IPv6' if valid IPv6, 'Neither' otherwise
5 */
6function validIPAddress(queryIP: string): string {
7    if (isIPv4(queryIP)) {
8        return 'IPv4';
9    }
10    if (isIPv6(queryIP)) {
11        return 'IPv6';
12    }
13    return 'Neither';
14}
15
16/**
17 * Checks if a string is a valid IPv4 address
18 * Valid IPv4: four decimal numbers separated by dots, each between 0-255
19 * No leading zeros allowed (except for '0' itself)
20 * @param s - The string to validate as IPv4
21 * @returns true if valid IPv4, false otherwise
22 */
23function isIPv4(s: string): boolean {
24    // IPv4 cannot end with a dot
25    if (s.endsWith('.')) {
26        return false;
27    }
28  
29    // Split by dots and check for exactly 4 segments
30    const segments: string[] = s.split('.');
31    if (segments.length !== 4) {
32        return false;
33    }
34  
35    // Validate each segment
36    for (const segment of segments) {
37        // Empty segments are invalid
38        // Segments with leading zeros (except single '0') are invalid
39        if (segment.length === 0 || (segment.length > 1 && segment[0] === '0')) {
40            return false;
41        }
42      
43        // Convert to number and validate range [0, 255]
44        const num: number = convert(segment);
45        if (num < 0 || num > 255) {
46            return false;
47        }
48    }
49  
50    return true;
51}
52
53/**
54 * Checks if a string is a valid IPv6 address
55 * Valid IPv6: eight hexadecimal groups separated by colons
56 * Each group is 1-4 hexadecimal digits
57 * @param s - The string to validate as IPv6
58 * @returns true if valid IPv6, false otherwise
59 */
60function isIPv6(s: string): boolean {
61    // IPv6 cannot end with a colon
62    if (s.endsWith(':')) {
63        return false;
64    }
65  
66    // Split by colons and check for exactly 8 segments
67    const segments: string[] = s.split(':');
68    if (segments.length !== 8) {
69        return false;
70    }
71  
72    // Validate each segment
73    for (const segment of segments) {
74        // Each segment must be 1-4 characters long
75        if (segment.length < 1 || segment.length > 4) {
76            return false;
77        }
78      
79        // Each character must be a hexadecimal digit
80        for (const char of segment) {
81            if (!isHexDigit(char)) {
82                return false;
83            }
84        }
85    }
86  
87    return true;
88}
89
90/**
91 * Converts a string to a number, validating that all characters are digits
92 * @param s - The string to convert
93 * @returns The numeric value if valid, -1 if invalid
94 */
95function convert(s: string): number {
96    let result: number = 0;
97  
98    for (const char of s) {
99        // Return -1 if any character is not a digit
100        if (!isDigit(char)) {
101            return -1;
102        }
103      
104        // Build the number digit by digit
105        result = result * 10 + (char.charCodeAt(0) - '0'.charCodeAt(0));
106      
107        // Early return if number exceeds 255
108        if (result > 255) {
109            return result;
110        }
111    }
112  
113    return result;
114}
115
116/**
117 * Checks if a character is a decimal digit (0-9)
118 * @param c - The character to check
119 * @returns true if the character is a digit, false otherwise
120 */
121function isDigit(c: string): boolean {
122    return c >= '0' && c <= '9';
123}
124
125/**
126 * Checks if a character is a hexadecimal digit (0-9, a-f, A-F)
127 * @param c - The character to check
128 * @returns true if the character is a hex digit, false otherwise
129 */
130function isHexDigit(c: string): boolean {
131    return (c >= '0' && c <= '9') || 
132           (c >= 'a' && c <= 'f') || 
133           (c >= 'A' && c <= 'F');
134}
135

Time and Space Complexity

Time Complexity: O(n)

The algorithm processes the input string queryIP of length n in the following ways:

  • The split() operation on either "." or ":" traverses the entire string once, taking O(n) time
  • For IPv4 validation: iterating through at most 4 segments, each with constant operations like isdigit() and integer conversion on segments of bounded size (max 3 digits)
  • For IPv6 validation: iterating through at most 8 segments, checking each character against the valid hexadecimal set, but the total characters checked across all segments is bounded by n
  • The character-by-character validation in IPv6 (all(c in "0123456789abcdefABCDEF" for c in t)) processes each character in the string at most once

Since each character in the input string is processed a constant number of times, the overall time complexity is O(n).

Space Complexity: O(n)

The space usage comes from:

  • The split() operation creates a new list containing segments of the original string. In the worst case, this stores all characters from the input string in the list format, requiring O(n) space
  • The string segments t are references to portions of the split result, not creating additional copies
  • All other variables (ss, loop variables) use constant space

Therefore, the space complexity is O(n) where n is the length of the input string queryIP.

Common Pitfalls

1. Not Handling Empty Segments Properly

A common mistake is not checking for empty segments that can occur with consecutive delimiters or trailing delimiters.

Problematic scenarios:

  • "192.168.1." - trailing dot creates an empty segment
  • "192..168.1.1" - consecutive dots create an empty segment
  • ":2001:db8:85a3:0:0:8A2E:0370:7334" - leading colon creates an empty segment

Why the current solution handles it correctly:

  • For IPv4: segment.isdigit() returns False for empty strings
  • For IPv6: The length check 1 <= len(segment) <= 4 fails for empty segments

2. Integer Overflow When Converting IPv4 Segments

When a segment contains a very large number like "99999999999999999999", calling int(segment) could theoretically cause issues in some languages, though Python handles arbitrary precision integers well.

Better approach for robustness:

# Add a length check before conversion
for segment in segments:
    # Limit segment length to prevent unnecessarily large numbers
    if len(segment) > 3:  # Max valid IPv4 segment is "255" (3 digits)
        return False
  
    if len(segment) > 1 and segment[0] == "0":
        return False
  
    if not segment.isdigit() or not 0 <= int(segment) <= 255:
        return False

3. Case Sensitivity Confusion in IPv6

Developers might forget that IPv6 accepts both uppercase and lowercase hexadecimal letters.

Common mistake:

# Wrong - only checks lowercase
if not all(char in "0123456789abcdef" for char in segment):
    return False

Correct approach (as in the solution):

# Includes both cases
if not all(char in "0123456789abcdefABCDEF" for char in segment):
    return False

4. Incorrect Leading Zero Validation for IPv4

A subtle bug occurs when checking for leading zeros without considering single "0" as valid.

Common mistake:

# Wrong - rejects valid "0" segments
if segment[0] == "0":
    return False

Correct approach (as in the solution):

# Only reject if length > 1 AND starts with "0"
if len(segment) > 1 and segment[0] == "0":
    return False

5. Not Validating Before Integer Conversion

Attempting to convert non-numeric strings to integers will raise an exception.

Common mistake:

# Wrong - will crash on "192.168.1.1a"
for segment in segments:
    if not 0 <= int(segment) <= 255:  # ValueError if segment contains letters
        return False

Correct approach (as in the solution):

# Check if it's numeric first
if not segment.isdigit() or not 0 <= int(segment) <= 255:
    return False

These pitfalls highlight the importance of thorough input validation and careful attention to edge cases when parsing structured string formats like IP addresses.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More