Maximum Points Inside the Square
MedPrompt
You are given a 2D array points and a string s where, points[i] represents the coordinates of point i, and s[i] represents the tag of point i.
A valid square is a square centered at the origin (0, 0), has edges parallel to the axes, and does not contain two points with the same tag.
Return the maximum number of points contained in a valid square.
Note:
- A point is considered to be inside the square if it lies on or within the square's boundaries.
- The side length of the square can be zero.
Example 1:

Input: points = [[2,2],[-1,-2],[-4,4],[-3,1],[3,-3]], s = "abdca"
Output: 2
Explanation:
The square of side length 4 covers two points points[0] and points[1].
Example 2:

Input: points = [[1,1],[-2,-2],[-2,2]], s = "abb"
Output: 1
Explanation:
The square of side length 2 covers one point, which is points[0].
Example 3:
Input: points = [[1,1],[-1,-1],[2,-2]], s = "ccd"
Output: 0
Explanation:
It's impossible to make any valid squares centered at the origin such that it covers only one point among points[0] and points[1].
Constraints:
1 <= s.length, points.length <= 105points[i].length == 2-109 <= points[i][0], points[i][1] <= 109s.length == points.lengthpointsconsists of distinct coordinates.sconsists only of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach calculates the effective distance of each point from the origin, which determines when it gets included in an expanding square. By sorting all points based on this distance, we can simulate the process of expanding the square and find the first moment a duplicate tag is introduced. The number of points included just before this collision is the answer.
Algorithm
- Calculate
dist(i) = max(|points[i][0]|, |points[i][1]|)for each pointi. - Create a list of pairs
(dist(i), s[i]). - Sort the list based on
dist(i). - Initialize
count = 0and an empty setseen_tags. - Iterate through the sorted list, processing points with the same distance in groups.
- For each group, check for tag collisions within the group and with
seen_tags. - If a collision is found, return the current
count. - Otherwise, add the group's tags to
seen_tags, updatecount, and proceed to the next group. - If the loop finishes, return the total number of points.
Walkthrough
For a point (x, y), the smallest square centered at the origin that contains it has a half-side length of max(|x|, |y|). Let's call this the point's "distance". The core idea is that as we increase the square's size, points are included in increasing order of their "distance". A square becomes invalid as soon as we include a point that has the same tag as a point already inside the square.
The algorithm proceeds as follows:
- Create a list of objects or pairs, where each element contains the calculated "distance" for a point and its corresponding tag.
- Sort this list in ascending order based on the distances. Points with the same distance can be ordered arbitrarily among themselves.
- Iterate through the sorted list, processing points with the same distance as a single group.
- Maintain a set of tags of points that are already included in our valid square (
seen_tags). - For each group of points with the same distance
d: a. First, check for duplicate tags within the group itself. If found, a square of half-sidedis invalid. The answer is the number of points processed so far. b. Next, check if any tag in the current group is already present inseen_tags. If so, this also makes the square of half-sidedinvalid. The answer is the number of points processed so far. c. If the group is valid, add all its tags toseen_tagsand add the group's size to the total count of points. - If the loop completes without finding any collisions, it means all
Npoints can form a valid set. The answer isN.
This method correctly identifies the largest set of points that can be contained in a valid square by finding the critical distance at which a collision first occurs.
class PointInfo { int dist; char tag; PointInfo(int dist, char tag) { this.dist = dist; this.tag = tag; }} public int maxPointsInsideSquare(int[][] points, String s) { int n = points.length; List<PointInfo> pointInfos = new ArrayList<>(); for (int i = 0; i < n; i++) { int dist = Math.max(Math.abs(points[i][0]), Math.abs(points[i][1])); pointInfos.add(new PointInfo(dist, s.charAt(i))); } Collections.sort(pointInfos, Comparator.comparingInt(p -> p.dist)); Set<Character> seenTags = new HashSet<>(); int count = 0; int i = 0; while (i < n) { int currentDist = pointInfos.get(i).dist; int j = i; // Find the end of the block with the same distance while (j < n && pointInfos.get(j).dist == currentDist) { j++; } // Process the block from i to j-1 Set<Character> blockTags = new HashSet<>(); for (int k = i; k < j; k++) { char tag = pointInfos.get(k).tag; // Check for collision within the block or with previously seen tags if (blockTags.contains(tag) || seenTags.contains(tag)) { return count; } blockTags.add(tag); } // No collision, add block tags to seen tags and update count seenTags.addAll(blockTags); count += (j - i); i = j; } return count;}Complexity
Time
O(N log N), where N is the number of points. The dominant operation is sorting the `N` points based on their distance. The subsequent iteration is O(N).
Space
O(N) to store the list of point information (distance and tag) for sorting.
Trade-offs
Pros
Conceptually straightforward simulation of an expanding square.
Correctly handles cases with multiple points at the same distance from the origin.
Cons
The O(N log N) time complexity from sorting can be suboptimal for very large inputs if a linear time solution exists.
This approach focuses on directly finding the maximum possible size of a valid square. A square becomes invalid if it contains two points with the same tag. For each tag, the critical event is when the second point with that tag enters the square. The smallest of these critical distances over all tags determines the maximum size for any valid square.
Algorithm
- Create a map from tags to a list of distances.
- Populate the map by iterating through all points, calculating their distance, and adding it to the list for their tag.
- Initialize
limit = infinity. - For each tag with at least two points, sort its distance list and update
limitwith the second smallest distance. - Initialize
count = 0. - Iterate through all points again. If a point's distance is less than
limit, incrementcount. - Return
count.
Walkthrough
The largest valid square is limited by the first tag collision. A collision for a tag c occurs when a square expands enough to include the second-closest point with tag c.
Let dist(i) = max(|points[i][0]|, |points[i][1]|). For each tag c, if there are points p_{c,1}, p_{c,2}, ... with this tag, their distances are d_{c,1}, d_{c,2}, .... If we sort these distances as d'_{c,1} <= d'_{c,2} <= ..., a square with half-side r >= d'_{c,2} will contain at least two points with tag c and will be invalid.
Therefore, the maximum half-side length for a valid square, let's call it limit, must be strictly less than the minimum of all d'_{c,2} values.
The algorithm is:
- Group points by their tags. A
Map<Character, List<Integer>>can be used to store the distances for each tag. - Iterate through all points, calculate their
dist, and add it to the list corresponding to their tag in the map. - Initialize a
limitvariable to infinity. - Iterate through the map. For each tag that has two or more points:
a. Sort the list of distances.
b. The second element in the sorted list,
dists.get(1), is the collision distance for this tag. c. Updatelimit = min(limit, dists.get(1)). - Once the
limitis found, the final step is to count how many points have a distance strictly less than thislimit. Iterate through all points one last time, and ifdist(i) < limit, increment a counter. - Return the final count. If no tag appears more than once, the
limitremains infinity, and all points are counted.
public int maxPointsInsideSquare(int[][] points, String s) { int n = points.length; Map<Character, List<Integer>> tagDists = new HashMap<>(); for (int i = 0; i < n; i++) { int dist = Math.max(Math.abs(points[i][0]), Math.abs(points[i][1])); char tag = s.charAt(i); tagDists.computeIfAbsent(tag, k -> new ArrayList<>()).add(dist); } int limit = Integer.MAX_VALUE; for (List<Integer> dists : tagDists.values()) { if (dists.size() >= 2) { Collections.sort(dists); limit = Math.min(limit, dists.get(1)); } } if (limit == Integer.MAX_VALUE) { return n; } int count = 0; for (int i = 0; i < n; i++) { int dist = Math.max(Math.abs(points[i][0]), Math.abs(points[i][1])); if (dist < limit) { count++; } } return count;}Complexity
Time
O(N + sum(n_c log n_c)), where `n_c` is the number of points with tag `c`. In the worst case (all points have the same tag), this is O(N log N). On average, it might be faster than O(N log N).
Space
O(N) to store the map where the total number of elements across all lists is N.
Trade-offs
Pros
More direct than simulating the square's expansion.
Can be more efficient than the general sorting approach if the number of points per tag is small, as
sum(n_c log n_c)can be less thanN log N.
Cons
Worst-case time complexity is still O(N log N) if all points share the same tag.
Requires O(N) extra space for the map and lists.
This approach improves upon the previous one by avoiding the costly sorting step. Instead of collecting all distances for a tag and then sorting, we can find the two smallest distances for each tag in a single pass. This allows us to determine the collision limit in linear time.
Algorithm
- Create two arrays,
min1andmin2, of size 26, initialized to infinity. - Create an array
distsof sizeNto store calculated distances. - Iterate through the
Npoints. For each point:- Calculate its distance
d. Store it indists. - Update the
min1andmin2arrays for the point's tag based ond.
- Calculate its distance
- Initialize
limit = infinity. - Find the minimum of all values in the
min2array. This is thelimit. - Count how many values in the
distsarray are less thanlimit. - Return the count.
Walkthrough
The key insight is that to find the collision distance for a tag, we only need its two smallest distances from the origin, not the entire sorted list of distances. We can find these two minimums efficiently.
The algorithm is as follows:
- We use a data structure to keep track of the two smallest distances found so far for each of the 26 possible tags. An array of pairs or a map
Map<Character, int[]>whereint[]is of size 2,[min1, min2], would work. Initialize all distances to infinity. - Iterate through all
Npoints once. For each pointi: a. Calculate its distanced = max(|points[i][0]|, |points[i][1]|). b. Get its tagc = s.charAt(i). c. Retrieve the current two minimums for tagc,[min1, min2]. d. Ifd < min1, it's the new smallest, so we updatemin2 = min1andmin1 = d. e. Else ifd < min2, it's the new second-smallest, so we updatemin2 = d. - After this single pass, we have the two smallest distances for every tag.
- Now, find the overall collision
limit. Initializelimit = infinity. Iterate through the 26 tags. The collision distance for a tag is itsmin2. Updatelimit = min(limit, min2)for each tag. - Finally, count the number of points inside the largest valid square. This is the number of points
iwhose distancedist(i)is strictly less than the calculatedlimit. This can be done by iterating through the points again. To avoid re-calculating distances, we can store them in an array during the first pass.
This approach processes each point a constant number of times, leading to a linear time solution.
public int maxPointsInsideSquare(int[][] points, String s) { int n = points.length; // min1[c], min2[c] store the two smallest distances for tag c int[] min1 = new int[26]; int[] min2 = new int[26]; Arrays.fill(min1, Integer.MAX_VALUE); Arrays.fill(min2, Integer.MAX_VALUE); int[] dists = new int[n]; for (int i = 0; i < n; i++) { int dist = Math.max(Math.abs(points[i][0]), Math.abs(points[i][1])); dists[i] = dist; int tagIndex = s.charAt(i) - 'a'; if (dist < min1[tagIndex]) { min2[tagIndex] = min1[tagIndex]; min1[tagIndex] = dist; } else if (dist < min2[tagIndex]) { min2[tagIndex] = dist; } } int limit = Integer.MAX_VALUE; for (int i = 0; i < 26; i++) { // min2 is the collision distance for this tag limit = Math.min(limit, min2[i]); } if (limit == Integer.MAX_VALUE) { return n; } int count = 0; for (int dist : dists) { if (dist < limit) { count++; } } return count;}Complexity
Time
O(N), as we iterate through the points a constant number of times.
Space
O(N) to store the pre-calculated distances. The space for tracking minimums per tag is O(1) as the alphabet size is constant. If we re-calculate distances in the final counting loop, space complexity can be reduced to O(1).
Trade-offs
Pros
Most efficient solution with linear time complexity.
Avoids comparison-based sorting.
Cons
Requires an extra pass or extra O(N) space to get the final count after determining the limit.
Solutions
Solution
class Solution {public int maxPointsInsideSquare(int[][] points, String s) { TreeMap<Integer, List<Integer>> g = new TreeMap<>(); for (int i = 0; i < points.length; ++i) { int x = points[i][0], y = points[i][1]; int key = Math.max(Math.abs(x), Math.abs(y)); g.computeIfAbsent(key, k->new ArrayList<>()).add(i); } boolean[] vis = new boolean[26]; int ans = 0; for (var idx : g.values()) { for (int i : idx) { int j = s.charAt(i) - 'a'; if (vis[j]) { return ans; } vis[j] = true; } ans += idx.size(); } return ans; }}class Solution {public: int maxPointsInsideSquare(vector<vector<int>> &points, string s) { map<int, vector<int>> g; for (int i = 0; i < points.size(); ++i) { auto &p = points[i]; int key = max(abs(p[0]), abs(p[1])); g[key].push_back(i); } bool vis[26]{}; int ans = 0; for (auto &[_, idx] : g) { for (int i : idx) { int j = s[i] - 'a'; if (vis[j]) { return ans; } vis[j] = true; } ans += idx.size(); } return ans; }};class Solution: def maxPointsInsideSquare(self, points: List[List[int]], s: str) -> int: g = defaultdict(list) for i, (x, y) in enumerate(points): g[max(abs(x), abs(y))]. append(i) vis = set() ans = 0 for d in sorted(g): idx = g[d] for i in idx: if s[i] in vis: return ans vis . add(s[i]) ans += len(idx) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.