Skip to content

LeetCode 468: Validate IP Address

A clear explanation of validating IPv4 and IPv6 addresses by checking segment count, length, characters, range, and leading-zero rules.

Problem Restatement

We are given a string queryIP.

We need to return one of three strings:

"IPv4"
"IPv6"
"Neither"

Return "IPv4" if the string is a valid IPv4 address.

Return "IPv6" if the string is a valid IPv6 address.

Otherwise, return "Neither".

A valid IPv4 address has four decimal parts separated by dots. Each part must be between 0 and 255, and leading zeros are not allowed. A valid IPv6 address has eight hexadecimal parts separated by colons. Each part must have length from 1 to 4, and leading zeros are allowed. The official problem asks us to classify queryIP as "IPv4", "IPv6", or "Neither".

Input and Output

ItemMeaning
InputA string queryIP
Output"IPv4", "IPv6", or "Neither"
IPv4 separatorDot .
IPv6 separatorColon :
IPv4 partsExactly 4
IPv6 partsExactly 8

Function shape:

def validIPAddress(queryIP: str) -> str:
    ...

Examples

Example 1:

queryIP = "172.16.254.1"

It has four dot-separated parts:

172
16
254
1

Each part is numeric, each value is between 0 and 255, and there are no leading zeros.

Answer:

"IPv4"

Example 2:

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

It has eight colon-separated parts.

Each part has length from 1 to 4.

Each character is a valid hexadecimal character.

Answer:

"IPv6"

Example 3:

queryIP = "256.256.256.256"

Each part is numeric, but 256 is larger than 255.

Answer:

"Neither"

Example 4:

queryIP = "192.168.01.1"

The part "01" has a leading zero.

Answer:

"Neither"

First Thought: Use Built-In IP Parsing

In real application code, we might use a standard library to parse IP addresses.

But in this problem, we need to implement the validation rules directly.

Also, the problem has exact rules:

TypeRule
IPv4Four decimal parts
IPv4No leading zeros unless the part is exactly "0"
IPv4Each value from 0 to 255
IPv6Eight hexadecimal parts
IPv6Each part has length 1 to 4
IPv6Leading zeros are allowed

So the clean approach is to write two helpers:

is_ipv4(queryIP)
is_ipv6(queryIP)

Then return the matching type.

Key Insight

IPv4 and IPv6 have different separators.

An IPv4 candidate uses dots:

.

An IPv6 candidate uses colons:

:

So we can check based on which separator appears.

But we still must validate every segment carefully.

For IPv4:

"172.16.254.1"

splits into:

["172", "16", "254", "1"]

For IPv6:

"2001:0db8:85a3:0:0:8A2E:0370:7334"

splits into:

["2001", "0db8", "85a3", "0", "0", "8A2E", "0370", "7334"]

The problem becomes segment validation.

IPv4 Validation Rules

A valid IPv4 address must satisfy all of these:

RuleExample validExample invalid
Exactly 4 parts"1.2.3.4""1.2.3"
Every part is non-empty"1.2.3.4""1..3.4"
Every part contains only digits"172""17a"
No leading zero if length > 1"0", "10""01"
Value is between 0 and 255"255""256"

IPv6 Validation Rules

A valid IPv6 address must satisfy all of these:

RuleExample validExample invalid
Exactly 8 partsEight groupsSeven groups
Every part length is 1 to 4"0", "0db8""", "02001"
Characters are hexadecimal"8A2E""037j"
Leading zeros are allowed"0000"Not invalid

The valid hexadecimal characters are:

0123456789abcdefABCDEF

Algorithm

Define is_ipv4(ip).

  1. Split by ".".
  2. If there are not exactly 4 parts, return False.
  3. For each part:
    1. If it is empty, return False.
    2. If it contains a non-digit character, return False.
    3. If it has length greater than 1 and starts with "0", return False.
    4. If its integer value is greater than 255, return False.
  4. Return True.

Define is_ipv6(ip).

  1. Split by ":".
  2. If there are not exactly 8 parts, return False.
  3. For each part:
    1. If its length is less than 1 or greater than 4, return False.
    2. If any character is not hexadecimal, return False.
  4. Return True.

Then:

  1. If is_ipv4(queryIP), return "IPv4".
  2. If is_ipv6(queryIP), return "IPv6".
  3. Return "Neither".

Correctness

The IPv4 helper checks exactly the IPv4 rules.

It requires exactly four parts, rejects empty parts, rejects non-digit characters, rejects leading zeros, and checks that each numeric value lies between 0 and 255.

Therefore, it returns True exactly for valid IPv4 addresses.

The IPv6 helper checks exactly the IPv6 rules.

It requires exactly eight parts, checks that every part has length from 1 to 4, and verifies that every character belongs to the hexadecimal character set.

Therefore, it returns True exactly for valid IPv6 addresses.

The main function returns "IPv4" if and only if the IPv4 helper accepts the input, "IPv6" if and only if the IPv6 helper accepts it, and "Neither" otherwise.

So the algorithm returns the required classification.

Complexity

Let n = len(queryIP).

MetricValueWhy
TimeO(n)Each character is checked a constant number of times
SpaceO(n)Splitting creates segment lists

The number of segments is bounded, but the split strings still depend on the input length.

Implementation

class Solution:
    def validIPAddress(self, queryIP: str) -> str:
        def is_ipv4(ip: str) -> bool:
            parts = ip.split(".")

            if len(parts) != 4:
                return False

            for part in parts:
                if part == "":
                    return False

                if not part.isdigit():
                    return False

                if len(part) > 1 and part[0] == "0":
                    return False

                if int(part) > 255:
                    return False

            return True

        def is_ipv6(ip: str) -> bool:
            parts = ip.split(":")

            if len(parts) != 8:
                return False

            valid = set("0123456789abcdefABCDEF")

            for part in parts:
                if len(part) < 1 or len(part) > 4:
                    return False

                for ch in part:
                    if ch not in valid:
                        return False

            return True

        if is_ipv4(queryIP):
            return "IPv4"

        if is_ipv6(queryIP):
            return "IPv6"

        return "Neither"

Code Explanation

The IPv4 checker starts by splitting on dots:

parts = ip.split(".")

A valid IPv4 address must have exactly four parts:

if len(parts) != 4:
    return False

Then each part is validated.

Empty parts are invalid:

if part == "":
    return False

Non-digit parts are invalid:

if not part.isdigit():
    return False

Leading zeros are invalid when the part has more than one character:

if len(part) > 1 and part[0] == "0":
    return False

The numeric range must be at most 255:

if int(part) > 255:
    return False

The IPv6 checker splits on colons:

parts = ip.split(":")

A valid IPv6 address must have exactly eight parts:

if len(parts) != 8:
    return False

Each part must have length from 1 to 4:

if len(part) < 1 or len(part) > 4:
    return False

Every character must be hexadecimal:

if ch not in valid:
    return False

The main function checks IPv4 first, then IPv6:

if is_ipv4(queryIP):
    return "IPv4"

if is_ipv6(queryIP):
    return "IPv6"

return "Neither"

Alternative Implementation With Separator Choice

We can avoid running both validators in many cases by checking the separator first.

class Solution:
    def validIPAddress(self, queryIP: str) -> str:
        if "." in queryIP:
            return "IPv4" if self._is_ipv4(queryIP) else "Neither"

        if ":" in queryIP:
            return "IPv6" if self._is_ipv6(queryIP) else "Neither"

        return "Neither"

    def _is_ipv4(self, ip: str) -> bool:
        parts = ip.split(".")

        if len(parts) != 4:
            return False

        for part in parts:
            if part == "":
                return False
            if not part.isdigit():
                return False
            if len(part) > 1 and part[0] == "0":
                return False
            if int(part) > 255:
                return False

        return True

    def _is_ipv6(self, ip: str) -> bool:
        parts = ip.split(":")

        if len(parts) != 8:
            return False

        valid = set("0123456789abcdefABCDEF")

        for part in parts:
            if not 1 <= len(part) <= 4:
                return False

            for ch in part:
                if ch not in valid:
                    return False

        return True

The first implementation is compact and works well.

Testing

def run_tests():
    s = Solution()

    assert s.validIPAddress("172.16.254.1") == "IPv4"
    assert s.validIPAddress("2001:0db8:85a3:0:0:8A2E:0370:7334") == "IPv6"
    assert s.validIPAddress("256.256.256.256") == "Neither"

    assert s.validIPAddress("192.168.01.1") == "Neither"
    assert s.validIPAddress("192.168.1.00") == "Neither"
    assert s.validIPAddress("[email protected]") == "Neither"

    assert s.validIPAddress("2001:0db8:85a3::8A2E:037j:7334") == "Neither"
    assert s.validIPAddress("02001:0db8:85a3:0000:0000:8a2e:0370:7334") == "Neither"

    assert s.validIPAddress("1.1.1.1.") == "Neither"
    assert s.validIPAddress("1..1.1") == "Neither"
    assert s.validIPAddress("0.0.0.0") == "IPv4"
    assert s.validIPAddress("255.255.255.255") == "IPv4"

    print("all tests passed")

run_tests()

Test meaning:

TestWhy
"172.16.254.1"Valid IPv4
Valid long IPv6Valid IPv6 with mixed case
"256.256.256.256"IPv4 parts out of range
"192.168.01.1"IPv4 leading zero
"192.168.1.00"IPv4 leading zero
"[email protected]"Invalid IPv4 character
IPv6 with ::Empty IPv6 group is invalid here
IPv6 group length 5IPv6 group too long
Trailing dotEmpty IPv4 group
Double dotEmpty IPv4 group
"0.0.0.0"Smallest IPv4 parts
"255.255.255.255"Largest IPv4 parts