LeetCode 2511 - Maximum Enemy Forts That Can Be Captured
In this problem, we are given an array called forts, where each position represents one of three possible states: - 1 means the fort belongs to us. - 0 means there is an enemy fort. - -1 means the position is empty.
Difficulty: 🟢 Easy
Topics: Array, Two Pointers
Solution
Problem Understanding
In this problem, we are given an array called forts, where each position represents one of three possible states:
1means the fort belongs to us.0means there is an enemy fort.-1means the position is empty.
We want to move our army from one of our forts to an empty position. However, the movement is only valid if every position between the starting fort and the destination is an enemy fort.
That condition is extremely important. Suppose we move from index i to index j. Then every position strictly between them must contain 0. We cannot pass through another friendly fort or another empty space.
While moving, every enemy fort passed along the way gets captured. Our goal is to compute the maximum number of enemy forts that can be captured in a single valid move.
The array length is at most 1000, which is relatively small. Even quadratic solutions are acceptable for this constraint. However, the structure of the problem allows us to design a simpler and more efficient linear-time solution.
The key observation is that a valid move always looks like this:
- one endpoint is
1 - the other endpoint is
-1 - every value between them is
0
Examples of valid segments:
[1,0,0,-1]
[-1,0,0,0,1]
Examples of invalid segments:
[1,0,-1,0] # extra values after destination
[1,0,1,-1] # another friendly fort in between
[1,-1] # captures 0 forts
Several edge cases are important:
- The array may contain no friendly fort at all.
- There may be no empty space
-1. - There may be no sequence of consecutive enemy forts between
1and-1. - Adjacent
1and-1capture zero forts. - Multiple valid segments may exist, and we must return the maximum captured count.
Approaches
Brute Force Approach
A straightforward solution is to try every pair of indices (i, j) where:
- one position contains
1 - the other contains
-1
For every such pair, we examine all positions between them to verify that they are all 0.
If the segment is valid, then the number of captured forts equals:
abs(i - j) - 1
because all positions between the endpoints are enemy forts.
This approach is correct because it explicitly checks every possible move and validates the problem constraints directly.
However, the implementation becomes inefficient because for every pair of positions we may scan the entire interval between them. In the worst case:
- there are
O(n^2)pairs - each validation takes
O(n)
leading to O(n^3) time complexity.
Even though n <= 1000 makes this technically feasible, it is unnecessarily slow.
Optimal Approach
The better approach comes from observing the structure of valid moves.
A valid move requires:
- one endpoint to be
1 - the other endpoint to be
-1 - everything between them to be
0
This means we only care about consecutive non-zero positions.
Whenever we encounter a non-zero value, we compare it with the previous non-zero value we saw:
- if they are opposite values (
1and-1) - then all positions between them must have been zeros
- therefore the segment is valid
The number of captured forts is simply the distance between these two non-zero positions minus one.
This allows us to solve the problem in one pass through the array.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n³) | O(1) | Checks every possible pair and validates every interval |
| Optimal | O(n) | O(1) | Tracks consecutive non-zero positions in one pass |
Algorithm Walkthrough
Optimal Linear Scan Algorithm
- Initialize two variables:
previous_index = -1max_captured = 0
The previous_index variable stores the most recent position containing either 1 or -1.
2. Iterate through the array from left to right.
3. For each index i, skip the position if forts[i] == 0.
Enemy forts themselves are not endpoints of moves, so they do not matter directly during scanning. 4. Whenever we encounter a non-zero value, check whether we have seen another non-zero position before.
If previous_index != -1, then we compare:
forts[i]forts[previous_index]
- If the two values are different, then the segment between them contains only zeros.
This works because:
- we only update
previous_indexwhen encountering non-zero values - therefore there cannot be another non-zero value between them
- Compute the captured forts:
captured = i - previous_index - 1
- Update the answer:
max_captured = max(max_captured, captured)
- Update
previous_index = i.
This prepares the algorithm for the next segment.
9. After the traversal finishes, return max_captured.
Why it works
The algorithm maintains the invariant that previous_index always points to the most recent non-zero position.
When we encounter another non-zero position:
- all positions between the two non-zero indices must be zeros
- otherwise we would have encountered another non-zero value earlier
Therefore, if the endpoint values are opposite (1 and -1), the segment is guaranteed to represent a valid move. Since every valid move must occur between consecutive non-zero positions, checking these pairs is sufficient to find the optimal answer.
Python Solution
from typing import List
class Solution:
def captureForts(self, forts: List[int]) -> int:
previous_index = -1
max_captured = 0
for current_index, value in enumerate(forts):
if value == 0:
continue
if previous_index != -1:
if forts[previous_index] != value:
captured = current_index - previous_index - 1
max_captured = max(max_captured, captured)
previous_index = current_index
return max_captured
The implementation follows the linear scan strategy exactly.
The variable previous_index stores the last seen non-zero position. During iteration, enemy forts (0) are ignored because they only matter as positions that may be captured.
Whenever we encounter another non-zero position, we compare the endpoint values. If they differ, then the segment between them is valid. The number of captured forts equals the number of positions between the two endpoints.
The expression:
current_index - previous_index - 1
computes exactly how many enemy forts lie between the two endpoints.
Finally, we update the maximum result and continue scanning.
Go Solution
func captureForts(forts []int) int {
previousIndex := -1
maxCaptured := 0
for currentIndex, value := range forts {
if value == 0 {
continue
}
if previousIndex != -1 {
if forts[previousIndex] != value {
captured := currentIndex - previousIndex - 1
if captured > maxCaptured {
maxCaptured = captured
}
}
}
previousIndex = currentIndex
}
return maxCaptured
}
The Go implementation mirrors the Python logic closely.
Go slices are dynamically sized views over arrays, so no special handling is required. Integer overflow is not a concern because the maximum array length is only 1000.
The manual maximum comparison replaces Python's built-in max() function.
Worked Examples
Example 1
forts = [1,0,0,-1,0,0,0,0,1]
Initial state:
previous_index = -1
max_captured = 0
| Index | Value | Action | Captured | max_captured |
|---|---|---|---|---|
| 0 | 1 | Set previous_index = 0 | - | 0 |
| 1 | 0 | Skip | - | 0 |
| 2 | 0 | Skip | - | 0 |
| 3 | -1 | Opposite endpoints | 2 | 2 |
| 4 | 0 | Skip | - | 2 |
| 5 | 0 | Skip | - | 2 |
| 6 | 0 | Skip | - | 2 |
| 7 | 0 | Skip | - | 2 |
| 8 | 1 | Opposite endpoints | 4 | 4 |
Final answer:
4
Example 2
forts = [0,0,1,-1]
Initial state:
previous_index = -1
max_captured = 0
| Index | Value | Action | Captured | max_captured |
|---|---|---|---|---|
| 0 | 0 | Skip | - | 0 |
| 1 | 0 | Skip | - | 0 |
| 2 | 1 | Set previous_index = 2 | - | 0 |
| 3 | -1 | Opposite endpoints | 0 | 0 |
The endpoints are adjacent, so no enemy forts are captured.
Final answer:
0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | The array is scanned exactly once |
| Space | O(1) | Only a few variables are used |
The algorithm is optimal because every element must be inspected at least once to determine whether it participates in a valid segment. No auxiliary data structures proportional to input size are required.
Test Cases
from typing import List
class Solution:
def captureForts(self, forts: List[int]) -> int:
previous_index = -1
max_captured = 0
for current_index, value in enumerate(forts):
if value == 0:
continue
if previous_index != -1:
if forts[previous_index] != value:
captured = current_index - previous_index - 1
max_captured = max(max_captured, captured)
previous_index = current_index
return max_captured
solution = Solution()
assert solution.captureForts([1,0,0,-1,0,0,0,0,1]) == 4
# Multiple valid moves, choose maximum
assert solution.captureForts([0,0,1,-1]) == 0
# Adjacent endpoints capture zero forts
assert solution.captureForts([1,0,-1]) == 1
# Single enemy fort captured
assert solution.captureForts([-1,0,0,1]) == 2
# Valid move in reverse direction
assert solution.captureForts([1,1,0,-1]) == 1
# Consecutive non-zero values break earlier segment
assert solution.captureForts([1,0,0,1]) == 0
# Same endpoint values are invalid
assert solution.captureForts([-1,0,0,-1]) == 0
# Same endpoint values again invalid
assert solution.captureForts([0,0,0]) == 0
# No valid endpoints
assert solution.captureForts([1]) == 0
# Single fort only
assert solution.captureForts([-1]) == 0
# Single empty position
assert solution.captureForts([1,0,0,0,-1]) == 3
# Long valid segment
assert solution.captureForts([1,-1,1,-1]) == 0
# All valid moves capture zero forts
Test Summary
| Test | Why |
|---|---|
[1,0,0,-1,0,0,0,0,1] |
Verifies choosing the maximum among multiple valid segments |
[0,0,1,-1] |
Verifies zero captured forts |
[1,0,-1] |
Smallest non-zero capture |
[-1,0,0,1] |
Ensures reverse-direction movement works |
[1,1,0,-1] |
Confirms consecutive non-zero values reset the segment |
[1,0,0,1] |
Same endpoints should not count |
[-1,0,0,-1] |
Another same-endpoint invalid case |
[0,0,0] |
No usable forts |
[1] |
Single-element boundary case |
[-1] |
Single empty position |
[1,0,0,0,-1] |
Long contiguous enemy segment |
[1,-1,1,-1] |
Adjacent endpoints produce zero captures |
Edge Cases
Adjacent 1 and -1
A very common bug is incorrectly treating adjacent endpoints as capturing one fort. Consider:
[1, -1]
There are no positions between the endpoints, so the correct answer is 0.
The implementation handles this correctly because:
captured = current_index - previous_index - 1
evaluates to:
1 - 0 - 1 = 0
Consecutive Non-Zero Values
Another tricky case occurs when non-zero values appear back to back:
[1, 1, 0, -1]
The first 1 cannot connect directly to -1 because another non-zero value interrupts the segment.
The algorithm handles this by always updating previous_index whenever a non-zero value is encountered. As a result, only consecutive non-zero endpoints are checked.
Arrays Without Valid Moves
Some arrays contain no valid movement at all:
[0,0,0]
[1,1,1]
[-1,-1]
A naive implementation might accidentally produce negative counts or invalid results.
This implementation safely returns 0 because:
max_capturedstarts at0- it only updates when a valid opposite-endpoint segment is found
- invalid segments are ignored automatically