1274. Number of Ships in a Rectangle π
Problem Description
This is an interactive problem where you need to count the number of ships in a rectangular area on a 2D plane without knowing their exact positions.
You're given:
- A rectangular search area defined by two points:
topRight
(upper-right corner) andbottomLeft
(lower-left corner) - An API function
Sea.hasShips(topRight, bottomLeft)
that returnstrue
if there's at least one ship within the specified rectangle (including boundaries), andfalse
otherwise - Each ship occupies exactly one integer coordinate point
- At most one ship can exist at any integer point
- There are at most 10 ships total in the search area
Your task is to determine the exact count of ships within the given rectangle using only the hasShips
API function. You cannot directly access the ship positions - you must work "blindfolded" by making strategic calls to hasShips
.
Key Constraints:
- You can make at most 400 calls to
hasShips
- Coordinates range from
0
to1000
- The solution uses a divide-and-conquer approach: recursively split the search area into four quadrants, check each quadrant for ships using
hasShips
, and only continue searching quadrants that contain ships - When a region is reduced to a single point (
x1 == x2
andy1 == y2
) and contains a ship, count it as 1 - The algorithm efficiently prunes empty regions to minimize API calls while ensuring all ships are counted
The challenge is to design an efficient search strategy that finds all ships within the API call limit, leveraging the fact that there are at most 10 ships to optimize the search pattern.
Intuition
The key insight is that we need to efficiently search a potentially large area (up to 1000 x 1000
grid) for at most 10 ships, using a limited number of API calls (400). If we checked each point individually, we'd need up to 1 million calls, which is far too many.
The breakthrough comes from realizing we can use a divide-and-conquer strategy similar to binary search, but in 2D space. The hasShips
API tells us if any ships exist in a rectangle, which we can leverage to intelligently prune our search space.
Here's the thought process:
-
Start with the whole rectangle - if
hasShips
returnsfalse
, we immediately know there are 0 ships and we're done with just 1 API call. -
If ships exist, divide the rectangle into smaller parts - but how should we divide? The natural choice is to split into 4 quadrants by dividing both horizontally and vertically at the midpoints. This gives us four sub-rectangles of roughly equal size.
-
Recursively search each quadrant - for each of the 4 quadrants, we call
hasShips
. If a quadrant has no ships, we skip it entirely (pruning that branch). If it has ships, we recursively divide it further. -
Base case: single point - when we've narrowed down to a single coordinate (
x1 == x2
andy1 == y2
), ifhasShips
returnstrue
, we've found exactly 1 ship.
The efficiency comes from the fact that with at most 10 ships, most quadrants will be empty and get pruned early. Each level of recursion divides the area by 4, so we quickly zoom in on the actual ship locations. The worst case would be if ships are spread out maximally, but even then, with only 10 ships, we won't need many recursive calls to isolate each one.
This approach naturally balances between:
- Broad coverage - we start by checking large areas
- Focused search - we only drill down into areas that actually contain ships
- Guaranteed completeness - we won't miss any ships since we systematically cover the entire area
Learn more about Divide and Conquer patterns.
Solution Approach
The solution implements a recursive divide-and-conquer algorithm that systematically narrows down the search space to locate all ships.
Implementation Details:
-
Main Function Structure:
- The
countShips
method initiates the search by calling a helper functiondfs
with the initial rectangle boundaries - The
dfs
function handles the recursive logic and returns the count of ships in its assigned rectangle
- The
-
Recursive Function
dfs(topRight, bottomLeft)
:First, extract the coordinates for cleaner code:
x1, y1 = bottomLeft.x, bottomLeft.y x2, y2 = topRight.x, topRight.y
-
Boundary Validation:
if x1 > x2 or y1 > y2: return 0
This handles invalid rectangles where the bottom-left is actually to the right or above the top-right.
-
Early Termination (Pruning):
if not sea.hasShips(topRight, bottomLeft): return 0
If the current rectangle contains no ships, immediately return 0. This is crucial for efficiency - it prevents unnecessary subdivision of empty areas.
-
Base Case - Single Point:
if x1 == x2 and y1 == y2: return 1
When the rectangle is reduced to a single point and we know it contains a ship (from the previous
hasShips
check), we've found exactly one ship. -
Divide into Four Quadrants:
Calculate the midpoints:
midx = (x1 + x2) >> 1 # Equivalent to (x1 + x2) // 2 midy = (y1 + y2) >> 1
The
>> 1
is a bitwise right shift, efficiently dividing by 2. -
Recursively Search Each Quadrant:
The rectangle is divided into four parts:
a = dfs(topRight, Point(midx + 1, midy + 1)) # Top-right quadrant b = dfs(Point(midx, y2), Point(x1, midy + 1)) # Top-left quadrant c = dfs(Point(midx, midy), bottomLeft) # Bottom-left quadrant d = dfs(Point(x2, midy), Point(midx + 1, y1)) # Bottom-right quadrant
The quadrants are defined as:
- Top-right: from
(midx + 1, midy + 1)
to(x2, y2)
- Top-left: from
(x1, midy + 1)
to(midx, y2)
- Bottom-left: from
(x1, y1)
to(midx, midy)
- Bottom-right: from
(midx + 1, y1)
to(x2, midy)
- Top-right: from
-
Aggregate Results:
return a + b + c + d
The total count is the sum of ships found in all four quadrants.
Why This Works:
- Each recursive call either finds ships in a smaller area or terminates early if no ships exist
- The search space reduces exponentially with each level (by a factor of 4)
- With at most 10 ships, most branches get pruned early, keeping the number of API calls well below 400
- The algorithm guarantees finding all ships since it systematically covers every point in the original rectangle
Time Complexity Analysis:
- In the worst case with ships spread out, we might need
O(ships * log(area))
API calls - With 10 ships and a
1000 x 1000
grid, this stays well within the 400-call limit
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach. Imagine we have a 4Γ4 grid (coordinates from 0 to 3) with 3 ships located at positions (1,1), (3,0), and (3,3).
Initial Call:
dfs(topRight=(3,3), bottomLeft=(0,0))
hasShips((3,3), (0,0))
returnstrue
(there are ships in the area)- Not a single point, so we divide into quadrants
- Calculate midpoints:
midx = 1, midy = 1
Level 1 - Four Quadrants:
-
Top-right quadrant:
dfs((3,3), (2,2))
hasShips((3,3), (2,2))
returnstrue
(ship at (3,3) is here)- Not a single point, divide further with
midx=2, midy=2
- Sub-quadrants:
dfs((3,3), (3,3))
: Single point,hasShips
returnstrue
β Count = 1dfs((2,3), (2,3))
: Single point,hasShips
returnsfalse
β Count = 0dfs((2,2), (2,2))
: Single point,hasShips
returnsfalse
β Count = 0dfs((3,2), (3,2))
: Single point,hasShips
returnsfalse
β Count = 0
- Total for this quadrant: 1
-
Top-left quadrant:
dfs((1,3), (0,2))
hasShips((1,3), (0,2))
returnsfalse
(no ships here)- Early termination β Count = 0
-
Bottom-left quadrant:
dfs((1,1), (0,0))
hasShips((1,1), (0,0))
returnstrue
(ship at (1,1) is here)- Not a single point, divide with
midx=0, midy=0
- Sub-quadrants:
dfs((1,1), (1,1))
: Single point,hasShips
returnstrue
β Count = 1dfs((0,1), (0,1))
: Single point,hasShips
returnsfalse
β Count = 0dfs((0,0), (0,0))
: Single point,hasShips
returnsfalse
β Count = 0dfs((1,0), (1,0))
: Single point,hasShips
returnsfalse
β Count = 0
- Total for this quadrant: 1
-
Bottom-right quadrant:
dfs((3,1), (2,0))
hasShips((3,1), (2,0))
returnstrue
(ship at (3,0) is here)- Not a single point, divide with
midx=2, midy=0
- Sub-quadrants:
dfs((3,1), (3,1))
: Single point,hasShips
returnsfalse
β Count = 0dfs((2,1), (2,1))
: Single point,hasShips
returnsfalse
β Count = 0dfs((2,0), (2,0))
: Single point,hasShips
returnsfalse
β Count = 0dfs((3,0), (3,0))
: Single point,hasShips
returnstrue
β Count = 1
- Total for this quadrant: 1
Final Result: 1 + 0 + 1 + 1 = 3 ships found
Key Observations:
- The top-left quadrant was pruned immediately (1 API call saved multiple recursive calls)
- Each ship was eventually isolated to a single point through recursive subdivision
- The algorithm made approximately 20 API calls for this example, well below the 400 limit
- Empty regions were efficiently skipped, focusing the search on areas with ships
Solution Implementation
1# """
2# This is Sea's API interface.
3# You should not implement it, or speculate about its implementation
4# """
5# class Sea:
6# def hasShips(self, topRight: 'Point', bottomLeft: 'Point') -> bool:
7#
8# class Point:
9# def __init__(self, x: int, y: int):
10# self.x = x
11# self.y = y
12
13
14class Solution:
15 def countShips(self, sea: "Sea", topRight: "Point", bottomLeft: "Point") -> int:
16 """
17 Count the number of ships in the given rectangle area using divide and conquer.
18
19 Args:
20 sea: Sea object with hasShips API to check if ships exist in a rectangle
21 topRight: Top-right corner of the search area
22 bottomLeft: Bottom-left corner of the search area
23
24 Returns:
25 Total number of ships in the given area
26 """
27
28 def search_area(top_right: "Point", bottom_left: "Point") -> int:
29 """
30 Recursively search for ships in the given rectangular area.
31 Uses divide and conquer by splitting the area into 4 quadrants.
32
33 Args:
34 top_right: Top-right corner of current search area
35 bottom_left: Bottom-left corner of current search area
36
37 Returns:
38 Number of ships found in the current area
39 """
40 # Extract coordinates for clarity
41 left_x, bottom_y = bottom_left.x, bottom_left.y
42 right_x, top_y = top_right.x, top_right.y
43
44 # Check if the rectangle is valid (not inverted)
45 if left_x > right_x or bottom_y > top_y:
46 return 0
47
48 # Early termination: no ships in this area
49 if not sea.hasShips(top_right, bottom_left):
50 return 0
51
52 # Base case: single point, contains exactly one ship
53 if left_x == right_x and bottom_y == top_y:
54 return 1
55
56 # Calculate midpoints to divide the area into quadrants
57 mid_x = (left_x + right_x) >> 1 # Bitwise right shift for integer division by 2
58 mid_y = (bottom_y + top_y) >> 1
59
60 # Recursively search all four quadrants
61 # Top-right quadrant
62 top_right_quadrant = search_area(
63 top_right,
64 Point(mid_x + 1, mid_y + 1)
65 )
66
67 # Top-left quadrant
68 top_left_quadrant = search_area(
69 Point(mid_x, top_y),
70 Point(left_x, mid_y + 1)
71 )
72
73 # Bottom-left quadrant
74 bottom_left_quadrant = search_area(
75 Point(mid_x, mid_y),
76 bottom_left
77 )
78
79 # Bottom-right quadrant
80 bottom_right_quadrant = search_area(
81 Point(right_x, mid_y),
82 Point(mid_x + 1, bottom_y)
83 )
84
85 # Return total count from all quadrants
86 return (top_right_quadrant + top_left_quadrant +
87 bottom_left_quadrant + bottom_right_quadrant)
88
89 # Start the recursive search from the entire area
90 return search_area(topRight, bottomLeft)
91
1/**
2 * // This is Sea's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class Sea {
5 * public boolean hasShips(int[] topRight, int[] bottomLeft);
6 * }
7 */
8
9class Solution {
10 /**
11 * Counts the number of ships in a rectangular sea area using divide and conquer.
12 * The algorithm recursively divides the search area into 4 quadrants and counts ships in each.
13 *
14 * @param sea The Sea object with hasShips API to check if ships exist in a given rectangle
15 * @param topRight The top-right corner coordinates [x, y] of the search rectangle
16 * @param bottomLeft The bottom-left corner coordinates [x, y] of the search rectangle
17 * @return The total number of ships in the specified rectangular area
18 */
19 public int countShips(Sea sea, int[] topRight, int[] bottomLeft) {
20 // Extract coordinates for better readability
21 int leftX = bottomLeft[0];
22 int bottomY = bottomLeft[1];
23 int rightX = topRight[0];
24 int topY = topRight[1];
25
26 // Base case: Invalid rectangle (coordinates are inverted)
27 if (leftX > rightX || bottomY > topY) {
28 return 0;
29 }
30
31 // Base case: No ships in this rectangle, skip further division
32 if (!sea.hasShips(topRight, bottomLeft)) {
33 return 0;
34 }
35
36 // Base case: Single point rectangle contains a ship
37 if (leftX == rightX && bottomY == topY) {
38 return 1;
39 }
40
41 // Calculate midpoints to divide the rectangle into 4 quadrants
42 int midX = (leftX + rightX) >> 1; // Equivalent to (leftX + rightX) / 2
43 int midY = (bottomY + topY) >> 1; // Equivalent to (bottomY + topY) / 2
44
45 // Recursively count ships in each quadrant:
46 // Top-right quadrant
47 int topRightQuadrant = countShips(sea, topRight, new int[] {midX + 1, midY + 1});
48
49 // Top-left quadrant
50 int topLeftQuadrant = countShips(sea, new int[] {midX, topY}, new int[] {leftX, midY + 1});
51
52 // Bottom-left quadrant
53 int bottomLeftQuadrant = countShips(sea, new int[] {midX, midY}, bottomLeft);
54
55 // Bottom-right quadrant
56 int bottomRightQuadrant = countShips(sea, new int[] {rightX, midY}, new int[] {midX + 1, bottomY});
57
58 // Return the total count from all quadrants
59 return topRightQuadrant + topLeftQuadrant + bottomLeftQuadrant + bottomRightQuadrant;
60 }
61}
62
1/**
2 * // This is Sea's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class Sea {
5 * public:
6 * bool hasShips(vector<int> topRight, vector<int> bottomLeft);
7 * };
8 */
9
10class Solution {
11public:
12 /**
13 * Counts the number of ships in a given rectangular area using divide and conquer
14 * @param sea - The Sea object with hasShips API to check if ships exist in a rectangle
15 * @param topRight - Top-right corner coordinates [x, y] of the search rectangle
16 * @param bottomLeft - Bottom-left corner coordinates [x, y] of the search rectangle
17 * @return The total number of ships in the specified rectangle
18 */
19 int countShips(Sea sea, vector<int> topRight, vector<int> bottomLeft) {
20 // Extract coordinates for clarity
21 int leftX = bottomLeft[0];
22 int bottomY = bottomLeft[1];
23 int rightX = topRight[0];
24 int topY = topRight[1];
25
26 // Base case: Invalid rectangle (coordinates are flipped)
27 if (leftX > rightX || bottomY > topY) {
28 return 0;
29 }
30
31 // Base case: No ships exist in this rectangle
32 if (!sea.hasShips(topRight, bottomLeft)) {
33 return 0;
34 }
35
36 // Base case: Single point rectangle contains exactly one ship
37 if (leftX == rightX && bottomY == topY) {
38 return 1;
39 }
40
41 // Calculate midpoints to divide the rectangle into 4 quadrants
42 int midX = (leftX + rightX) >> 1; // Equivalent to (leftX + rightX) / 2
43 int midY = (bottomY + topY) >> 1; // Equivalent to (bottomY + topY) / 2
44
45 // Recursively count ships in each quadrant:
46 // Top-right quadrant
47 int topRightQuadrant = countShips(sea, topRight, {midX + 1, midY + 1});
48
49 // Top-left quadrant
50 int topLeftQuadrant = countShips(sea, {midX, topY}, {leftX, midY + 1});
51
52 // Bottom-left quadrant
53 int bottomLeftQuadrant = countShips(sea, {midX, midY}, bottomLeft);
54
55 // Bottom-right quadrant
56 int bottomRightQuadrant = countShips(sea, {rightX, midY}, {midX + 1, bottomY});
57
58 // Return the sum of ships in all four quadrants
59 return topRightQuadrant + topLeftQuadrant + bottomLeftQuadrant + bottomRightQuadrant;
60 }
61};
62
1/**
2 * // This is the Sea's API interface.
3 * // You should not implement it, or speculate about its implementation
4 * class Sea {
5 * hasShips(topRight: number[], bottomLeft: number[]): boolean {}
6 * }
7 */
8
9/**
10 * Counts the number of ships in a rectangular sea area using divide and conquer approach.
11 * The algorithm recursively divides the search area into 4 quadrants and counts ships in each.
12 *
13 * @param sea - The Sea object that provides the hasShips API
14 * @param topRight - Coordinates [x, y] of the top-right corner of the search area
15 * @param bottomLeft - Coordinates [x, y] of the bottom-left corner of the search area
16 * @returns The total number of ships in the specified rectangular area
17 */
18function countShips(sea: Sea, topRight: number[], bottomLeft: number[]): number {
19 // Extract coordinates for better readability
20 const [bottomLeftX, bottomLeftY] = bottomLeft;
21 const [topRightX, topRightY] = topRight;
22
23 // Base case: Invalid rectangle or no ships in this area
24 if (bottomLeftX > topRightX || bottomLeftY > topRightY || !sea.hasShips(topRight, bottomLeft)) {
25 return 0;
26 }
27
28 // Base case: Single point contains exactly one ship
29 if (bottomLeftX === topRightX && bottomLeftY === topRightY) {
30 return 1;
31 }
32
33 // Calculate midpoints to divide the rectangle into 4 quadrants
34 const midX = (bottomLeftX + topRightX) >> 1; // Bitwise right shift for integer division
35 const midY = (bottomLeftY + topRightY) >> 1;
36
37 // Recursively count ships in each quadrant:
38 // Top-right quadrant
39 const topRightQuadrant = countShips(sea, topRight, [midX + 1, midY + 1]);
40
41 // Top-left quadrant
42 const topLeftQuadrant = countShips(sea, [midX, topRightY], [bottomLeftX, midY + 1]);
43
44 // Bottom-left quadrant
45 const bottomLeftQuadrant = countShips(sea, [midX, midY], bottomLeft);
46
47 // Bottom-right quadrant
48 const bottomRightQuadrant = countShips(sea, [topRightX, midY], [midX + 1, bottomLeftY]);
49
50 // Return the sum of ships in all quadrants
51 return topRightQuadrant + topLeftQuadrant + bottomLeftQuadrant + bottomRightQuadrant;
52}
53
Time and Space Complexity
Time Complexity: O(C Γ log(max(m, n)))
The algorithm uses a divide-and-conquer approach by recursively dividing the search area into 4 quadrants. The key insights for time complexity analysis are:
-
When there are no ships in a region (
hasShips
returns false), that branch terminates immediately withO(1)
work. -
When a region contains ships, it's divided into 4 smaller regions. The division continues until we reach individual cells (where
x1 == x2
andy1 == y2
). -
The depth of recursion is
O(log(max(m, n)))
because we're halving the larger dimension at each level. -
For each ship, the algorithm explores a path from the root to a leaf node containing that ship. While multiple ships might share parts of their paths, each ship contributes at most
O(log(max(m, n)))
nodes to the recursion tree. -
Since we have
C
ships total, and each contributesO(log(max(m, n)))
work, the total time complexity isO(C Γ log(max(m, n)))
.
Space Complexity: O(log(max(m, n)))
The space complexity is determined by the maximum depth of the recursion stack. Since we divide the search space in half at each recursive level (using midx
and midy
), the maximum recursion depth is O(log(max(m, n)))
, where m
and n
represent the dimensions of the initial rectangle. This occurs when we need to drill down from the largest dimension to a single cell.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Quadrant Boundaries
One of the most common mistakes is incorrectly defining the boundaries when dividing the rectangle into quadrants. This can lead to:
- Overlapping regions: Counting the same ship multiple times
- Gaps between quadrants: Missing ships that fall in the gaps
Example of incorrect division:
# WRONG - Creates overlapping quadrants at the midpoint top_right_quadrant = search_area(top_right, Point(mid_x, mid_y)) top_left_quadrant = search_area(Point(mid_x, top_y), Point(left_x, mid_y)) # Ships at (mid_x, mid_y) would be counted multiple times!
Correct approach:
# Ensure no overlap by using mid_x + 1 and mid_y + 1 for appropriate quadrants top_right_quadrant = search_area(top_right, Point(mid_x + 1, mid_y + 1)) top_left_quadrant = search_area(Point(mid_x, top_y), Point(left_x, mid_y + 1)) bottom_left_quadrant = search_area(Point(mid_x, mid_y), bottom_left) bottom_right_quadrant = search_area(Point(right_x, mid_y), Point(mid_x + 1, bottom_y))
2. Missing the Early Termination Check
Forgetting to check hasShips()
before recursing deeper wastes API calls and can exceed the 400-call limit.
Incorrect implementation:
def search_area(top_right, bottom_left):
# WRONG - No early termination check
if left_x == right_x and bottom_y == top_y:
# This might return 1 even for empty points!
return 1 if sea.hasShips(top_right, bottom_left) else 0
# Continues to divide even for empty areas
mid_x = (left_x + right_x) >> 1
mid_y = (bottom_y + top_y) >> 1
# ... recursive calls
Correct approach:
def search_area(top_right, bottom_left):
# Check for ships FIRST
if not sea.hasShips(top_right, bottom_left):
return 0 # Don't recurse into empty areas
# Only then check if it's a single point
if left_x == right_x and bottom_y == top_y:
return 1 # We know there's a ship here from the check above
3. Incorrect Base Case Logic
The base case must verify that we've reached a single point AND that point contains a ship (already verified by the early termination check).
Common mistake:
# WRONG - Doesn't ensure we're at a single point if sea.hasShips(top_right, bottom_left): return 1 # This could be a large area with multiple ships!
Correct approach:
# First check if area has ships (done earlier) # Then check if it's a single point if left_x == right_x and bottom_y == top_y: return 1 # Single point with a ship
4. Integer Division Pitfall
When calculating midpoints, ensure proper integer division to avoid floating-point coordinates.
Potential issue:
# In Python 3, this returns a float if not careful mid_x = (left_x + right_x) / 2 # WRONG - returns float
Correct approaches:
mid_x = (left_x + right_x) // 2 # Integer division # OR mid_x = (left_x + right_x) >> 1 # Bitwise shift (faster)
5. Not Handling Edge Cases
Failing to handle invalid rectangles where bottom_left is actually above or to the right of top_right.
Missing validation:
def search_area(top_right, bottom_left):
# WRONG - No validation
if not sea.hasShips(top_right, bottom_left):
return 0
# This might crash or behave unexpectedly for invalid rectangles
Correct approach:
def search_area(top_right, bottom_left):
# Validate rectangle first
if left_x > right_x or bottom_y > top_y:
return 0 # Invalid rectangle
if not sea.hasShips(top_right, bottom_left):
return 0
6. Order of Operations in Base Case
The order of checks matters for efficiency and correctness.
Inefficient ordering:
# WRONG ORDER - Checks single point before checking if ships exist if left_x == right_x and bottom_y == top_y: return 1 if sea.hasShips(top_right, bottom_left) else 0 # Wastes an API call for the base case
Optimal ordering:
# Check for ships first (prunes empty branches) if not sea.hasShips(top_right, bottom_left): return 0 # Then check if single point (no additional API call needed) if left_x == right_x and bottom_y == top_y: return 1
By avoiding these pitfalls, the solution efficiently finds all ships while minimizing API calls and ensuring correctness.
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!