3464. Maximize the Distance Between Points on a Square

  • Array
  • Binary Search
  • Greedy
TIME
O(n k log n * log(perimeter/k))
SPACE
O(n)
problem on leetcode ↗source on github ↗

Intuition

Key insight: For points on a square boundary, Manhattan distance equals the minimum of clockwise and counter-clockwise perimeter distances.

  1. Map 2D boundary points to 1D perimeter positions
  2. Binary search on the minimum distance
  3. Greedy check: can we select k points with all consecutive gaps >= d?

Solution

Step 1: Map to Perimeter Positions

Traverse boundary clockwise from (0,0):

  • Bottom (y=0): position = x
  • Right (x=side): position = side + y
  • Top (y=side): position = 2*side + (side - x)
  • Left (x=0): position = 3*side + (side - y)

Step 2: Binary Search + Greedy Check

For a candidate minimum distance d:

  • Try each point as starting point
  • Greedily select next point with gap >= d
  • After selecting k points, verify wrap-around gap is also >= d
def canAchieve(d):
    for start in range(n):
        count = 1
        last_idx = start
        
        for _ in range(k - 1):
            # Binary search for next valid position
            target = extended[last_idx] + d
            next_idx = bisect_left(extended, target, last_idx + 1, start + n)
            
            if next_idx >= start + n:
                break
            count += 1
            last_idx = next_idx
        
        if count == k:
            wrap_dist = extended[start + n] - extended[last_idx]
            if wrap_dist >= d:
                return True
    return False

Complexity

  • Time: O(n × k × log n × log(perimeter/k))
  • Space: O(n)

Edge Cases

  • All points at corners
  • Points evenly distributed
  • k equals number of points

Code

from typing import List
from bisect import bisect_left


class Solution:
    def maxDistance(self, side: int, points: List[List[int]], k: int) -> int:
        """
        3464. Maximize the Distance Between Points on a Square
        Time: O(n * k * log n * log(perimeter/k))
        Space: O(n)
        """
        perimeter = 4 * side

        def to_perimeter_pos(x: int, y: int) -> int:
            if y == 0:
                return x
            elif x == side:
                return side + y
            elif y == side:
                return 2 * side + (side - x)
            else:
                return 3 * side + (side - y)

        positions = sorted(to_perimeter_pos(x, y) for x, y in points)
        n = len(positions)

        # Double the array for cyclic handling
        extended = positions + [p + perimeter for p in positions]

        def canAchieve(d: int) -> bool:
            for start in range(n):
                count = 1
                last_idx = start

                for _ in range(k - 1):
                    target = extended[last_idx] + d
                    next_idx = bisect_left(extended, target, last_idx + 1, start + n)

                    if next_idx >= start + n:
                        break

                    count += 1
                    last_idx = next_idx

                if count == k:
                    wrap_dist = extended[start + n] - extended[last_idx]
                    if wrap_dist >= d:
                        return True

            return False

        lo, hi = 1, perimeter // k
        ans = 1

        while lo <= hi:
            mid = (lo + hi) // 2
            if canAchieve(mid):
                ans = mid
                lo = mid + 1
            else:
                hi = mid - 1

        return ans