2511. Maximum Enemy Forts That Can Be Captured
Problem Description
You have an array forts
representing positions with different types of forts. Each position can have one of three values:
-1
: Empty position (no fort)0
: Enemy fort1
: Your fort (under your command)
You want to move your army from one of your forts (position with value 1
) to an empty position (position with value -1
). The movement must follow these rules:
- The army can only travel through enemy forts (positions with value
0
) - All positions between the starting fort and destination must contain enemy forts
- When the army moves, it captures all enemy forts along the path
The goal is to find the maximum number of enemy forts you can capture in a single move.
For example, if you have [1, 0, 0, -1, 0, 0, 0, 1]
, you could move from position 0 (your fort) to position 3 (empty), capturing 2 enemy forts in between. Or you could move from position 7 (your fort) to position 3 (empty), capturing 3 enemy forts. The maximum would be 3.
Return 0
if:
- You have no forts under your command
- No valid moves are possible (no path exists between your fort and an empty position through only enemy forts)
The solution uses two pointers to find all valid movement paths. For each position i
that contains either your fort or an empty position, it finds the next non-enemy position j
. If forts[i] + forts[j] = 0
(meaning one is 1
and the other is -1
), then a valid move exists between them, capturing j - i - 1
enemy forts.
Intuition
The key insight is recognizing that a valid army movement requires exactly three components in sequence: a starting point (your fort or empty position), a path of enemy forts, and an ending point (empty position or your fort). The crucial observation is that the start and end points must be of opposite types - one must be your fort (1
) and the other must be empty (-1
).
Why does forts[i] + forts[j] = 0
work as our validation check? Because if one position has value 1
and the other has value -1
, their sum equals zero. This elegantly captures the requirement that we need opposite endpoints for a valid move.
The two-pointer approach naturally emerges from this pattern. We can scan through the array looking for non-zero positions (potential start/end points). When we find one at position i
, we look for the next non-zero position at j
. Everything between them must be enemy forts by definition - if there were any non-enemy positions in between, our pointer j
would have stopped earlier.
The algorithm essentially identifies all possible "segments" in the array where:
- The segment starts and ends with non-zero values
- Everything in between is zeros (enemy forts)
- The start and end values are opposites (sum to zero)
By checking all such segments and tracking the maximum length minus 2 (excluding the endpoints), we find the maximum number of enemy forts that can be captured. The pointer i
jumps directly to j
after each check because we've already examined everything up to position j
, making the algorithm efficient with O(n) time complexity.
Learn more about Two Pointers patterns.
Solution Approach
The solution uses a two-pointer technique to efficiently find all valid army movements and determine the maximum number of enemy forts that can be captured.
Algorithm Steps:
-
Initialize variables: Set pointer
i = 0
to traverse the array, andans = 0
to track the maximum enemy forts captured. -
Outer loop: While
i < n
, process each position:- Start with
j = i + 1
as the second pointer - Check if
forts[i]
is non-zero (potential starting point)
- Start with
-
Inner traversal: If
forts[i] != 0
:- Move pointer
j
forward whileforts[j] == 0
(enemy forts) - This finds the next non-zero position after
i
- Move pointer
-
Validate movement: Once
j
stops at a non-zero position:- Check if
j < n
(within bounds) andforts[i] + forts[j] == 0
- This condition ensures one position is
1
(your fort) and the other is-1
(empty) - If valid, the number of captured enemy forts is
j - i - 1
- Check if
-
Update maximum: Compare with current
ans
and keep the maximum value -
Advance pointer: Set
i = j
to skip already processed positions
Why this works:
- The algorithm identifies segments of consecutive zeros (enemy forts) bounded by non-zero values
- By checking
forts[i] + forts[j] == 0
, we ensure valid movement between a fort and empty position - Setting
i = j
after each iteration avoids redundant checks and maintains O(n) time complexity - Each position is visited at most twice (once by
i
, once byj
), ensuring linear time
Example walkthrough with forts = [1, 0, 0, -1, 0, 1]
:
i=0, forts[0]=1
,j
moves to 3 whereforts[3]=-1
1 + (-1) = 0
β, captured forts:3 - 0 - 1 = 2
i=3, forts[3]=-1
,j
moves to 5 whereforts[5]=1
(-1) + 1 = 0
β, captured forts:5 - 3 - 1 = 1
- Maximum captured:
2
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 the solution with forts = [1, 0, 0, -1, 0, 0, 0, 1]
Initial state:
i = 0
,ans = 0
- Array:
[1, 0, 0, -1, 0, 0, 0, 1]
^i
First iteration:
forts[i] = 1
(non-zero, potential start point)- Set
j = i + 1 = 1
- Move
j
through enemy forts:j=1
(0),j=2
(0),j=3
(-1, stop!) - Check validity:
forts[0] + forts[3] = 1 + (-1) = 0
β - Enemy forts captured:
3 - 0 - 1 = 2
- Update
ans = 2
, seti = 3
Second iteration:
i = 3
,forts[i] = -1
(non-zero)- Set
j = 4
- Move
j
through enemy forts:j=4
(0),j=5
(0),j=6
(0),j=7
(1, stop!) - Check validity:
forts[3] + forts[7] = -1 + 1 = 0
β - Enemy forts captured:
7 - 3 - 1 = 3
- Update
ans = 3
, seti = 7
Third iteration:
i = 7
,forts[i] = 1
(non-zero)- Set
j = 8
, butj >= n
, so incrementi++
i = 8 >= n
, loop ends
Result: Maximum enemy forts captured = 3
The algorithm found two valid moves:
- Fort at position 0 β Empty at position 3 (captures 2 enemy forts)
- Empty at position 3 β Fort at position 7 (captures 3 enemy forts)
The maximum is 3, achieved by moving from position 7 to position 3.
Solution Implementation
1class Solution:
2 def captureForts(self, forts: List[int]) -> int:
3 """
4 Find the maximum number of enemy forts that can be captured.
5
6 The array contains:
7 - 1: friendly fort
8 - -1: enemy fort
9 - 0: empty position
10
11 We can capture enemy forts between a friendly fort and an enemy fort.
12
13 Args:
14 forts: List of integers representing fort positions
15
16 Returns:
17 Maximum number of enemy forts (0s) that can be captured
18 """
19 n = len(forts)
20 current_index = 0
21 max_captured = 0
22
23 # Iterate through the forts array
24 while current_index < n:
25 next_index = current_index + 1
26
27 # If current position has a fort (either friendly or enemy)
28 if forts[current_index] != 0:
29 # Count consecutive empty positions (0s)
30 while next_index < n and forts[next_index] == 0:
31 next_index += 1
32
33 # Check if we found an opposite fort (friendly to enemy or vice versa)
34 # forts[i] + forts[j] == 0 means one is 1 and the other is -1
35 if next_index < n and forts[current_index] + forts[next_index] == 0:
36 # Calculate number of empty positions between the two forts
37 captured_count = next_index - current_index - 1
38 max_captured = max(max_captured, captured_count)
39
40 # Move to the next non-zero position or end
41 current_index = next_index
42
43 return max_captured
44
1class Solution {
2 /**
3 * Captures the maximum number of enemy forts between your fort and an empty position.
4 *
5 * @param forts Array where 1 represents your fort, -1 represents enemy fort, 0 represents empty position
6 * @return Maximum number of enemy forts that can be captured in one move
7 */
8 public int captureForts(int[] forts) {
9 int arrayLength = forts.length;
10 int maxCaptured = 0;
11 int currentIndex = 0;
12
13 // Iterate through the array to find valid capture sequences
14 while (currentIndex < arrayLength) {
15 int nextNonZeroIndex = currentIndex + 1;
16
17 // Check if current position is a fort (either yours or enemy's)
18 if (forts[currentIndex] != 0) {
19 // Count consecutive empty positions (zeros)
20 while (nextNonZeroIndex < arrayLength && forts[nextNonZeroIndex] == 0) {
21 nextNonZeroIndex++;
22 }
23
24 // Check if we found a valid capture sequence:
25 // Must end with opposite fort type (sum equals 0: 1 + (-1) = 0 or (-1) + 1 = 0)
26 if (nextNonZeroIndex < arrayLength &&
27 forts[currentIndex] + forts[nextNonZeroIndex] == 0) {
28 // Calculate number of captured forts (empty positions between)
29 int capturedCount = nextNonZeroIndex - currentIndex - 1;
30 maxCaptured = Math.max(maxCaptured, capturedCount);
31 }
32 }
33
34 // Move to the next non-zero position or next position to check
35 currentIndex = nextNonZeroIndex;
36 }
37
38 return maxCaptured;
39 }
40}
41
1class Solution {
2public:
3 int captureForts(vector<int>& forts) {
4 int arraySize = forts.size();
5 int maxCapturedForts = 0;
6 int currentIndex = 0;
7
8 // Iterate through the forts array
9 while (currentIndex < arraySize) {
10 int nextIndex = currentIndex + 1;
11
12 // Check if current position is a fort (1) or command center (-1)
13 if (forts[currentIndex] != 0) {
14 // Count consecutive empty positions (0s)
15 while (nextIndex < arraySize && forts[nextIndex] == 0) {
16 ++nextIndex;
17 }
18
19 // Check if we found the opposite type of position
20 // (fort to command center or vice versa)
21 if (nextIndex < arraySize && forts[currentIndex] + forts[nextIndex] == 0) {
22 // Update maximum captured forts between two opposite positions
23 maxCapturedForts = max(maxCapturedForts, nextIndex - currentIndex - 1);
24 }
25 }
26
27 // Move to the next non-empty position or next position to check
28 currentIndex = nextIndex;
29 }
30
31 return maxCapturedForts;
32 }
33};
34
1/**
2 * Calculates the maximum number of enemy forts that can be captured
3 * by moving army from one friendly fort to another.
4 *
5 * @param forts - Array where 1 represents friendly fort, -1 represents enemy fort, 0 represents empty position
6 * @returns Maximum number of enemy forts (0s) that can be captured in a single move
7 */
8function captureForts(forts: number[]): number {
9 const fortsCount: number = forts.length;
10 let maxCaptured: number = 0;
11 let currentIndex: number = 0;
12
13 // Iterate through all positions in the forts array
14 while (currentIndex < fortsCount) {
15 let nextIndex: number = currentIndex + 1;
16
17 // Check if current position has a fort (either friendly or enemy)
18 if (forts[currentIndex] !== 0) {
19 // Count consecutive empty positions (potential captures)
20 while (nextIndex < fortsCount && forts[nextIndex] === 0) {
21 nextIndex++;
22 }
23
24 // Check if we found an opposite fort (friendly to enemy or enemy to friendly)
25 // Sum of opposite forts equals 0 (1 + (-1) = 0 or (-1) + 1 = 0)
26 if (nextIndex < fortsCount && forts[currentIndex] + forts[nextIndex] === 0) {
27 // Calculate number of empty positions between the two forts
28 const capturedCount: number = nextIndex - currentIndex - 1;
29 maxCaptured = Math.max(maxCaptured, capturedCount);
30 }
31 }
32
33 // Move to the next non-empty position or beyond current segment
34 currentIndex = nextIndex;
35 }
36
37 return maxCaptured;
38}
39
Time and Space Complexity
Time Complexity: O(n)
The algorithm uses a two-pointer approach where pointer i
traverses through the array. In each iteration:
- When
forts[i]
is non-zero (either 1 or -1), the inner while loop moves pointerj
forward to find the next non-zero element - After the inner loop completes, pointer
i
is set toj
, ensuring each element is visited at most once
Even though there's a nested loop structure, each element in the array is examined exactly once. The outer loop moves i
through the array, and the inner loop moves j
, but i
jumps to j
after each iteration, preventing revisiting elements. Therefore, the total number of operations is proportional to n
.
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space:
- Variables
i
,j
,ans
, andn
are integer variables - No additional data structures are created
- The space usage doesn't scale with the input size
The algorithm operates in-place without requiring additional memory that grows with the input size, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem Statement
A critical pitfall is misinterpreting what values represent what in the array:
-1
represents an empty position (NOT an enemy fort)0
represents an enemy fort1
represents your fort
Many developers mistakenly think -1
represents enemy forts since it's negative, leading to completely incorrect logic. The solution captures enemy forts (0s) between your fort (1) and an empty position (-1).
Solution: Carefully read the problem statement and add clear comments documenting what each value represents.
2. Incorrect Movement Validation
A common mistake is checking for valid movement using conditions like:
# WRONG: This doesn't ensure opposite fort types if forts[i] != 0 and forts[j] != 0: captured = j - i - 1
This would incorrectly count movements between two of your own forts [1, 0, 0, 1]
or two empty positions [-1, 0, 0, -1]
as valid.
Solution: Use forts[i] + forts[j] == 0
to ensure one position is 1
and the other is -1
.
3. Off-by-One Error in Counting
Developers often make mistakes when counting captured forts:
# WRONG: Includes the endpoints captured = j - i # WRONG: Only subtracts one endpoint captured = j - i
Solution: The correct formula is j - i - 1
to exclude both endpoints and count only the enemy forts in between.
4. Inefficient Pointer Movement
Setting i = i + 1
instead of i = j
after processing leads to O(nΒ²) complexity:
# INEFFICIENT: Revisits positions unnecessarily while i < n: # ... process ... i += 1 # WRONG: Should be i = j
Solution: Always set i = j
after processing to skip already examined positions and maintain O(n) complexity.
5. Missing Edge Cases
Forgetting to handle arrays with:
- No friendly forts:
[0, 0, -1, 0]
- No empty positions:
[1, 0, 0, 0]
- No enemy forts between valid endpoints:
[1, -1, 0, 0]
Solution: The algorithm naturally handles these by returning 0 when no valid moves exist, but ensure your implementation doesn't assume these elements exist.
Which data structure is used to implement priority queue?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!