Check if CIDR is fully canceled by rules
Company: Databricks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates understanding of IPv4 CIDR arithmetic, set operations on IP ranges (overlap detection and subtraction), and ordered rule semantics for allow/deny processing.
Constraints
- 0 <= len(rules) <= 2000
- All CIDR strings are valid IPv4 CIDRs of the form `a.b.c.d/x` with `0 <= x <= 32`
- Rules must be processed sequentially in the given order
- Your solution must correctly handle splitting the remaining region into multiple disjoint intervals after an `allow` rule
Examples
Input: ("10.0.0.0/24", [("allow", "10.0.0.0/25"), ("allow", "10.0.0.128/25")])
Expected Output: True
Explanation: The first allow removes the lower half of the /24, and the second removes the upper half, so nothing remains.
Input: ("10.0.0.0/24", [("allow", "10.0.0.0/25")])
Expected Output: False
Explanation: Only the first 128 IPs are removed. The upper half of the target still remains.
Input: ("192.168.1.0/24", [("allow", "192.168.1.0/25"), ("deny", "192.168.1.128/26"), ("allow", "192.168.1.128/25")])
Expected Output: False
Explanation: After removing the lower half, the remaining region is `192.168.1.128/25`. The deny rule overlaps part of that remaining region, so the answer is immediately False.
Input: ("10.0.0.0/24", [("allow", "10.0.0.0/25"), ("deny", "10.0.0.0/26"), ("allow", "10.0.0.128/25")])
Expected Output: True
Explanation: The deny rule only touches addresses already removed by the first allow, so it does not overlap the current remaining region. The final allow removes the rest.
Input: ("10.0.0.0/24", [("allow", "10.0.0.64/26"), ("allow", "10.0.0.0/26"), ("allow", "10.0.0.128/25")])
Expected Output: True
Explanation: The first allow removes the middle quarter and splits the remaining region into two pieces. The next two allows remove both pieces completely.
Input: ("0.0.0.0/0", [])
Expected Output: False
Explanation: With no rules, the target is never canceled, so the remaining region is not empty.
Input: ("10.0.1.5/24", [("allow", "10.0.1.200/25"), ("allow", "10.0.1.1/25")])
Expected Output: True
Explanation: Both CIDRs are interpreted by prefix, so they cover the upper and lower halves of `10.0.1.0/24`. Together they remove the whole target.
Solution
def solution(target, rules):
MAX32 = 0xFFFFFFFF
def ip_to_int(ip):
a, b, c, d = map(int, ip.split('.'))
return (a << 24) | (b << 16) | (c << 8) | d
def parse_cidr(cidr):
ip_str, prefix_str = cidr.split('/')
prefix = int(prefix_str)
ip = ip_to_int(ip_str)
if prefix == 0:
start = 0
end = MAX32
else:
mask = (MAX32 << (32 - prefix)) & MAX32
start = ip & mask
end = start | (MAX32 ^ mask)
return start, end
t_start, t_end = parse_cidr(target)
remaining = [(t_start, t_end)]
for rule_type, cidr in rules:
r_start, r_end = parse_cidr(cidr)
if rule_type == 'deny':
for a, b in remaining:
if b < r_start:
continue
if a > r_end:
break
return False
else:
new_remaining = []
for a, b in remaining:
if b < r_start or a > r_end:
new_remaining.append((a, b))
else:
if a < r_start:
new_remaining.append((a, r_start - 1))
if r_end < b:
new_remaining.append((r_end + 1, b))
remaining = new_remaining
if not remaining:
return True
return not remainingTime complexity: O(r * k) where `r` is the number of rules and `k` is the number of remaining intervals at each step; worst-case O(r^2). Space complexity: O(r) worst-case for storing the disjoint remaining intervals.
Hints
- Convert each CIDR into an inclusive integer interval `[start, end]` using 32-bit IPv4 arithmetic.
- Maintain the uncanceled part of the target as a sorted list of disjoint intervals. An `allow` subtracts from these intervals, while a `deny` only needs to detect whether any interval overlaps.