Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given an integer side, representing the edge length of a square with corners at (0, 0), (0, side), (side, 0), and (side, side) on a Cartesian plane.
You are also given a positive integer k and a 2D integer array points, where points[i] = [xi, yi] represents the coordinate of a point lying on the boundary of the square.
You need to select k elements among points such that the minimum Manhattan distance between any two points is maximized.
Return the maximum possible minimum Manhattan distance between the selected k points.
The Manhattan Distance between two cells (xi, yi) and (xj, yj) is |xi - xj| + |yi - yj|.
Example 1:
Input: side = 2, points = [[0,2],[2,0],[2,2],[0,0]], k = 4
Output: 2
Explanation:
Select all four points.
Example 2:
Input: side = 2, points = [[0,0],[1,2],[2,0],[2,2],[2,1]], k = 4
Output: 1
Explanation:
Select the points (0, 0), (2, 0), (2, 2), and (2, 1).
Example 3:
Input: side = 2, points = [[0,0],[0,1],[0,2],[1,2],[2,0],[2,2],[2,1]], k = 5
Output: 1
Explanation:
Select the points (0, 0), (0, 1), (0, 2), (1, 2), and (2, 2).
Constraints:
1 <= side <= 1094 <= points.length <= min(4 * side, 15 * 103)points[i] == [xi, yi]points[i] lies on the boundary of the square.points[i] are unique.4 <= k <= min(25, points.length)Problem summary: You are given an integer side, representing the edge length of a square with corners at (0, 0), (0, side), (side, 0), and (side, side) on a Cartesian plane. You are also given a positive integer k and a 2D integer array points, where points[i] = [xi, yi] represents the coordinate of a point lying on the boundary of the square. You need to select k elements among points such that the minimum Manhattan distance between any two points is maximized. Return the maximum possible minimum Manhattan distance between the selected k points. The Manhattan Distance between two cells (xi, yi) and (xj, yj) is |xi - xj| + |yi - yj|.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Math · Binary Search
2 [[0,2],[2,0],[2,2],[0,0]] 4
2 [[0,0],[1,2],[2,0],[2,2],[2,1]] 4
2 [[0,0],[0,1],[0,2],[1,2],[2,0],[2,2],[2,1]] 5
maximum-number-of-integers-to-choose-from-a-range-ii)maximum-points-inside-the-square)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
class Solution {
public int maxDistance(int side, int[][] points, int k) {
List<int[]> ordered = getOrderedPoints(side, points);
int l = 0;
int r = side;
while (l < r) {
final int m = (l + r + 1) / 2;
if (isValidDistance(ordered, k, m))
l = m;
else
r = m - 1;
}
return l;
}
private record Sequence(int startX, int startY, int endX, int endY, int length) {}
// Returns true if we can select `k` points such that the minimum Manhattan
// distance between any two consecutive chosen points is at least `m`.
private boolean isValidDistance(List<int[]> ordered, int k, int d) {
Deque<Sequence> dq = new ArrayDeque<>(List.of(new Sequence(
ordered.get(0)[0], ordered.get(0)[1], ordered.get(0)[0], ordered.get(0)[1], 1)));
int maxLength = 1;
for (int i = 1; i < ordered.size(); ++i) {
final int x = ordered.get(i)[0];
final int y = ordered.get(i)[1];
int startX = x;
int startY = y;
int length = 1;
while (!dq.isEmpty() &&
(Math.abs(x - dq.peekFirst().endX()) + Math.abs(y - dq.peekFirst().endY()) >= d)) {
if (Math.abs(x - dq.peekFirst().startX()) + Math.abs(y - dq.peekFirst().startY()) >= d &&
dq.peekFirst().length() + 1 >= length) {
startX = dq.peekFirst().startX();
startY = dq.peekFirst().startY();
length = dq.peekFirst().length() + 1;
maxLength = Math.max(maxLength, length);
}
dq.pollFirst();
}
dq.addLast(new Sequence(startX, startY, x, y, length));
}
return maxLength >= k;
}
// Returns the ordered points on the perimeter of a square of side length
// `side`, starting from left, top, right, and bottom boundaries.
private List<int[]> getOrderedPoints(int side, int[][] points) {
List<int[]> left = new ArrayList<>();
List<int[]> top = new ArrayList<>();
List<int[]> right = new ArrayList<>();
List<int[]> bottom = new ArrayList<>();
for (int[] point : points) {
final int x = point[0];
final int y = point[1];
if (x == 0 && y > 0)
left.add(point);
else if (x > 0 && y == side)
top.add(point);
else if (x == side && y < side)
right.add(point);
else
bottom.add(point);
}
left.sort(Comparator.comparingInt(a -> a[1]));
top.sort(Comparator.comparingInt(a -> a[0]));
right.sort(Comparator.comparingInt(a -> - a[1]));
bottom.sort(Comparator.comparingInt(a -> - a[0]));
List<int[]> ordered = new ArrayList<>();
ordered.addAll(left);
ordered.addAll(top);
ordered.addAll(right);
ordered.addAll(bottom);
return ordered;
}
}
// Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
package main
import (
"math"
"math/bits"
"slices"
"sort"
)
// https://space.bilibili.com/206214
func maxDistance(side int, points [][]int, k int) int {
n := len(points)
a := make([]int, n)
for i, p := range points {
x, y := p[0], p[1]
if x == 0 {
a[i] = y
} else if y == side {
a[i] = side + x
} else if x == side {
a[i] = side*3 - y
} else {
a[i] = side*4 - x
}
}
slices.Sort(a)
f := make([]int, n+1)
end := make([]int, n) // 改成保存 a[i],但 C++/Java 这样写就得用 64 位整数了
ans := sort.Search(side*4/k, func(low int) bool {
low++
j := n
for i := n - 1; i >= 0; i-- {
x := a[i]
for a[j-1] >= x+low {
j--
}
f[i] = f[j] + 1
if f[i] == 1 {
end[i] = x // i 自己就是最后一个点
} else {
end[i] = end[j]
}
if f[i] == k && end[i]-x <= side*4-low {
return false
}
}
return true
})
return ans
}
func maxDistance4(side int, points [][]int, k int) int {
n := len(points)
a := make([]int, n, n+1)
for i, p := range points {
x, y := p[0], p[1]
if x == 0 {
a[i] = y
} else if y == side {
a[i] = side + x
} else if x == side {
a[i] = side*3 - y
} else {
a[i] = side*4 - x
}
}
slices.Sort(a)
a = append(a, math.MaxInt) // 哨兵
g := make([][]int, n+1)
ans := sort.Search(side*4/k, func(low int) bool {
low++
clear(g)
j := n
for i := n - 1; i >= 0; i-- {
for a[j-1] >= a[i]+low {
j--
}
g[j] = append(g[j], i) // 建树
}
st := []int{}
var dfs func(int) bool
dfs = func(x int) bool {
st = append(st, a[x])
m := len(st)
// 注意栈中多了一个 a[n],所以是 m > k 不是 >=
if m > k && st[m-k]-a[x] <= side*4-low {
return true
}
for _, y := range g[x] {
if dfs(y) {
return true
}
}
st = st[:m-1] // 恢复现场
return false
}
return !dfs(n)
})
return ans
}
func maxDistance3(side int, points [][]int, k int) int {
n := len(points)
a := make([]int, n)
for i, p := range points {
x, y := p[0], p[1]
if x == 0 {
a[i] = y
} else if y == side {
a[i] = side + x
} else if x == side {
a[i] = side*3 - y
} else {
a[i] = side*4 - x
}
}
slices.Sort(a)
k-- // 往后跳 k-1 步,这里先减一,方便计算
highBit := bits.Len(uint(k)) - 1
nxt := make([][5]int, n+1) // 5 可以改为 highBit+1(用 array 而不是 slice,提高访问效率)
for j := range nxt[n] {
nxt[n][j] = n // 哨兵
}
ans := sort.Search(side*4/k, func(low int) bool {
low++
// 预处理倍增数组 nxt
j := n
for i := n - 1; i >= 0; i-- {
for a[j-1] >= a[i]+low {
j--
}
nxt[i][0] = j
for k := 1; k <= highBit; k++ {
nxt[i][k] = nxt[nxt[i][k-1]][k-1]
}
}
// 枚举起点
for i, start := range a {
// 往后跳 k-1 步(注意上面把 k 减一了)
cur := i
for j := highBit; j >= 0; j-- {
if k>>j&1 > 0 {
cur = nxt[cur][j]
}
}
if cur == n { // 出界
break
}
if a[cur]-start <= side*4-low {
return false
}
}
return true
})
return ans
}
func maxDistance22(side int, points [][]int, k int) int {
a := make([]int, len(points))
for i, p := range points {
x, y := p[0], p[1]
if x == 0 {
a[i] = y
} else if y == side {
a[i] = side + x
} else if x == side {
a[i] = side*3 - y
} else {
a[i] = side*4 - x
}
}
slices.Sort(a)
ans := sort.Search(side*4/k, func(low int) bool {
low++
idx := make([]int, k)
cur := a[0]
for j, i := 1, 0; j < k; j++ {
i += sort.Search(len(a)-i, func(j int) bool { return a[i+j] >= cur+low })
if i == len(a) {
return true
}
idx[j] = i
cur = a[i]
}
if cur-a[0] <= side*4-low {
return false
}
// 第一个指针移动到第二个指针的位置,就不用继续枚举了
end0 := idx[1]
for idx[0]++; idx[0] < end0; idx[0]++ {
for j := 1; j < k; j++ {
for a[idx[j]] < a[idx[j-1]]+low {
idx[j]++
if idx[j] == len(a) {
return true
}
}
}
if a[idx[k-1]]-a[idx[0]] <= side*4-low {
return false
}
}
return true
})
return ans
}
func maxDistance21(side int, points [][]int, k int) int {
a := make([]int, len(points))
for i, p := range points {
x, y := p[0], p[1]
if x == 0 {
a[i] = y
} else if y == side {
a[i] = side + x
} else if x == side {
a[i] = side*3 - y
} else {
a[i] = side*4 - x
}
}
slices.Sort(a)
ans := sort.Search(side*4/k, func(low int) bool {
low++
idx := make([]int, k)
for {
for j := 1; j < k; j++ {
for a[idx[j]] < a[idx[j-1]]+low {
idx[j]++
if idx[j] == len(a) {
return true
}
}
}
if a[idx[k-1]]-a[idx[0]] <= side*4-low {
return false
}
idx[0]++
}
})
return ans
}
# Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
from dataclasses import dataclass
@dataclass(frozen=True)
class Sequence:
startX: int
startY: int
endX: int
endY: int
length: int
def __iter__(self):
yield self.startX
yield self.startY
yield self.endX
yield self.endY
yield self.length
class Solution:
def maxDistance(self, side: int, points: list[list[int]], k: int) -> int:
ordered = self._getOrderedPoints(side, points)
def isValidDistance(m: int) -> bool:
"""
Returns True if we can select `k` points such that the minimum Manhattan
distance between any two consecutive chosen points is at least `m`.
"""
dq = collections.deque([Sequence(*ordered[0], *ordered[0], 1)])
maxLength = 1
for i in range(1, len(ordered)):
x, y = ordered[i]
startX, startY = ordered[i]
length = 1
while dq and abs(x - dq[0].endX) + abs(y - dq[0].endY) >= m:
if (abs(x - dq[0].startX) + abs(y - dq[0].startY) >= m
and dq[0].length + 1 >= length):
startX = dq[0].startX
startY = dq[0].startY
length = dq[0].length + 1
maxLength = max(maxLength, length)
dq.popleft()
dq.append(Sequence(startX, startY, x, y, length))
return maxLength >= k
l = 0
r = side
while l < r:
m = (l + r + 1) // 2
if isValidDistance(m):
l = m
else:
r = m - 1
return l
def _getOrderedPoints(self, side: int, points: list[list[int]]) -> list[list[int]]:
"""
Returns the ordered points on the perimeter of a square of side length
`side`, starting from left, top, right, and bottom boundaries.
"""
left = sorted([(x, y) for x, y in points if x == 0 and y > 0])
top = sorted([(x, y) for x, y in points if x > 0 and y == side])
right = sorted([(x, y) for x, y in points if x == side and y < side],
reverse=True)
bottom = sorted([(x, y) for x, y in points if y == 0], reverse=True)
return left + top + right + bottom
// Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
// class Solution {
// public int maxDistance(int side, int[][] points, int k) {
// List<int[]> ordered = getOrderedPoints(side, points);
// int l = 0;
// int r = side;
//
// while (l < r) {
// final int m = (l + r + 1) / 2;
// if (isValidDistance(ordered, k, m))
// l = m;
// else
// r = m - 1;
// }
//
// return l;
// }
//
// private record Sequence(int startX, int startY, int endX, int endY, int length) {}
//
// // Returns true if we can select `k` points such that the minimum Manhattan
// // distance between any two consecutive chosen points is at least `m`.
// private boolean isValidDistance(List<int[]> ordered, int k, int d) {
// Deque<Sequence> dq = new ArrayDeque<>(List.of(new Sequence(
// ordered.get(0)[0], ordered.get(0)[1], ordered.get(0)[0], ordered.get(0)[1], 1)));
// int maxLength = 1;
//
// for (int i = 1; i < ordered.size(); ++i) {
// final int x = ordered.get(i)[0];
// final int y = ordered.get(i)[1];
// int startX = x;
// int startY = y;
// int length = 1;
// while (!dq.isEmpty() &&
// (Math.abs(x - dq.peekFirst().endX()) + Math.abs(y - dq.peekFirst().endY()) >= d)) {
// if (Math.abs(x - dq.peekFirst().startX()) + Math.abs(y - dq.peekFirst().startY()) >= d &&
// dq.peekFirst().length() + 1 >= length) {
// startX = dq.peekFirst().startX();
// startY = dq.peekFirst().startY();
// length = dq.peekFirst().length() + 1;
// maxLength = Math.max(maxLength, length);
// }
// dq.pollFirst();
// }
// dq.addLast(new Sequence(startX, startY, x, y, length));
// }
//
// return maxLength >= k;
// }
//
// // Returns the ordered points on the perimeter of a square of side length
// // `side`, starting from left, top, right, and bottom boundaries.
// private List<int[]> getOrderedPoints(int side, int[][] points) {
// List<int[]> left = new ArrayList<>();
// List<int[]> top = new ArrayList<>();
// List<int[]> right = new ArrayList<>();
// List<int[]> bottom = new ArrayList<>();
//
// for (int[] point : points) {
// final int x = point[0];
// final int y = point[1];
// if (x == 0 && y > 0)
// left.add(point);
// else if (x > 0 && y == side)
// top.add(point);
// else if (x == side && y < side)
// right.add(point);
// else
// bottom.add(point);
// }
//
// left.sort(Comparator.comparingInt(a -> a[1]));
// top.sort(Comparator.comparingInt(a -> a[0]));
// right.sort(Comparator.comparingInt(a -> - a[1]));
// bottom.sort(Comparator.comparingInt(a -> - a[0]));
// List<int[]> ordered = new ArrayList<>();
// ordered.addAll(left);
// ordered.addAll(top);
// ordered.addAll(right);
// ordered.addAll(bottom);
// return ordered;
// }
// }
// Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3464: Maximize the Distance Between Points on a Square
// class Solution {
// public int maxDistance(int side, int[][] points, int k) {
// List<int[]> ordered = getOrderedPoints(side, points);
// int l = 0;
// int r = side;
//
// while (l < r) {
// final int m = (l + r + 1) / 2;
// if (isValidDistance(ordered, k, m))
// l = m;
// else
// r = m - 1;
// }
//
// return l;
// }
//
// private record Sequence(int startX, int startY, int endX, int endY, int length) {}
//
// // Returns true if we can select `k` points such that the minimum Manhattan
// // distance between any two consecutive chosen points is at least `m`.
// private boolean isValidDistance(List<int[]> ordered, int k, int d) {
// Deque<Sequence> dq = new ArrayDeque<>(List.of(new Sequence(
// ordered.get(0)[0], ordered.get(0)[1], ordered.get(0)[0], ordered.get(0)[1], 1)));
// int maxLength = 1;
//
// for (int i = 1; i < ordered.size(); ++i) {
// final int x = ordered.get(i)[0];
// final int y = ordered.get(i)[1];
// int startX = x;
// int startY = y;
// int length = 1;
// while (!dq.isEmpty() &&
// (Math.abs(x - dq.peekFirst().endX()) + Math.abs(y - dq.peekFirst().endY()) >= d)) {
// if (Math.abs(x - dq.peekFirst().startX()) + Math.abs(y - dq.peekFirst().startY()) >= d &&
// dq.peekFirst().length() + 1 >= length) {
// startX = dq.peekFirst().startX();
// startY = dq.peekFirst().startY();
// length = dq.peekFirst().length() + 1;
// maxLength = Math.max(maxLength, length);
// }
// dq.pollFirst();
// }
// dq.addLast(new Sequence(startX, startY, x, y, length));
// }
//
// return maxLength >= k;
// }
//
// // Returns the ordered points on the perimeter of a square of side length
// // `side`, starting from left, top, right, and bottom boundaries.
// private List<int[]> getOrderedPoints(int side, int[][] points) {
// List<int[]> left = new ArrayList<>();
// List<int[]> top = new ArrayList<>();
// List<int[]> right = new ArrayList<>();
// List<int[]> bottom = new ArrayList<>();
//
// for (int[] point : points) {
// final int x = point[0];
// final int y = point[1];
// if (x == 0 && y > 0)
// left.add(point);
// else if (x > 0 && y == side)
// top.add(point);
// else if (x == side && y < side)
// right.add(point);
// else
// bottom.add(point);
// }
//
// left.sort(Comparator.comparingInt(a -> a[1]));
// top.sort(Comparator.comparingInt(a -> a[0]));
// right.sort(Comparator.comparingInt(a -> - a[1]));
// bottom.sort(Comparator.comparingInt(a -> - a[0]));
// List<int[]> ordered = new ArrayList<>();
// ordered.addAll(left);
// ordered.addAll(top);
// ordered.addAll(right);
// ordered.addAll(bottom);
// return ordered;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Check every element from left to right until we find the target or exhaust the array. Each comparison is O(1), and we may visit all n elements, giving O(n). No extra space needed.
Each comparison eliminates half the remaining search space. After k comparisons, the space is n/2ᵏ. We stop when the space is 1, so k = log₂ n. No extra memory needed — just two pointers (lo, hi).
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.
Usually fails on: Two-element ranges never converge.
Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.