A clear explanation of counting battleships in a board using one-pass observation without modifying the grid.
Problem Restatement
We are given a 2D board containing:
| Character | Meaning |
|---|---|
"X" | Part of a battleship |
"." | Empty water |
Battleships follow these rules:
- Battleships are placed either horizontally or vertically.
- Battleships cannot touch each other horizontally, vertically, or diagonally.
- A battleship consists of one or more consecutive
"X"cells.
Return the total number of battleships on the board.
The official follow-up asks whether we can solve the problem in one pass using only constant extra memory and without modifying the board. The constraints allow up to 200 x 200 boards. (leetcode.com)
Input and Output
| Item | Meaning |
|---|---|
| Input | 2D board of "X" and "." |
| Output | Number of battleships |
| Ship orientation | Horizontal or vertical only |
| Separation rule | Ships never touch each other |
Example function shape:
def countBattleships(board: list[list[str]]) -> int:
...Examples
Example 1:
image_group{“layout”:“carousel”,“aspect_ratio”:“16:9”,“query”:[“leetcode 419 battleships in a board example”,“battleship board grid example”,“count battleships matrix illustration”,“horizontal and vertical battleship example”],“num_per_query”:1}
board = [
["X", ".", ".", "X"],
[".", ".", ".", "X"],
[".", ".", ".", "X"],
]The answer is:
2Explanation:
| Battleship | Cells |
|---|---|
| Horizontal/Single | (0,0) |
| Vertical | (0,3), (1,3), (2,3) |
Example 2:
board = [["."]]The answer is:
0Example 3:
board = [["X"]]The answer is:
1First Thought: DFS or BFS
One natural approach is:
- Scan the grid.
- When we find an unvisited
"X", start DFS or BFS. - Mark the whole ship as visited.
- Increment the answer.
This works.
But the problem guarantees ships never touch each other.
That allows a simpler one-pass solution with constant extra space.
Key Insight
Every battleship has exactly one top-left starting cell.
For example:
X X XThe first cell is the ship start.
The later cells belong to the same ship.
Similarly:
X
X
XAgain, only the top cell is the start.
So while scanning the board:
A cell starts a new battleship exactly when:
- The cell contains
"X". - There is no
"X"directly above it. - There is no
"X"directly to its left.
That means this cell is the top-left corner of a ship.
Every battleship contributes exactly one such cell.
Algorithm
Initialize:
count = 0Scan every cell (r, c).
For each cell:
- If the cell is
".", skip it. - If the cell above is
"X", skip it. - If the cell to the left is
"X", skip it. - Otherwise, this cell is the start of a new battleship:
- Increment
count.
- Increment
Return count.
Correctness
Consider any battleship on the board.
Because ships are straight horizontal or vertical segments, the battleship has exactly one top-leftmost cell.
For that cell:
- There is no
"X"above it. - There is no
"X"to its left.
Therefore, the algorithm counts that cell.
Now consider any other cell belonging to the same battleship.
If the ship is horizontal, then that cell has an "X" to its left.
If the ship is vertical, then that cell has an "X" above it.
So the algorithm skips every non-start cell.
Thus each battleship is counted exactly once.
Since every counted cell corresponds to a unique battleship start, and every battleship has exactly one such start cell, the final count is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(mn) | Every cell is visited once |
| Space | O(1) | Only a counter is used |
Here:
| Symbol | Meaning |
|---|---|
m | Number of rows |
n | Number of columns |
Implementation
from typing import List
class Solution:
def countBattleships(self, board: List[List[str]]) -> int:
rows = len(board)
cols = len(board[0])
count = 0
for r in range(rows):
for c in range(cols):
if board[r][c] == ".":
continue
if r > 0 and board[r - 1][c] == "X":
continue
if c > 0 and board[r][c - 1] == "X":
continue
count += 1
return countCode Explanation
We first get the board size:
rows = len(board)
cols = len(board[0])The variable:
countstores the number of battleships found.
We scan every cell:
for r in range(rows):
for c in range(cols):If the cell is empty water:
if board[r][c] == ".":
continuethen it cannot start a battleship.
If the cell above is part of a ship:
if r > 0 and board[r - 1][c] == "X":
continuethen the current cell belongs to the same vertical ship.
If the cell to the left is part of a ship:
if c > 0 and board[r][c - 1] == "X":
continuethen the current cell belongs to the same horizontal ship.
If neither condition holds, this cell is the unique top-left start of a battleship:
count += 1Finally:
return countreturns the total number of ships.
Alternative DFS Solution
A DFS approach also works.
from typing import List
class Solution:
def countBattleships(self, board: List[List[str]]) -> int:
rows = len(board)
cols = len(board[0])
visited = [[False] * cols for _ in range(rows)]
def dfs(r: int, c: int) -> None:
if r < 0 or r >= rows or c < 0 or c >= cols:
return
if board[r][c] != "X":
return
if visited[r][c]:
return
visited[r][c] = True
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
ships = 0
for r in range(rows):
for c in range(cols):
if board[r][c] == "X" and not visited[r][c]:
ships += 1
dfs(r, c)
return shipsThis version is easier to generalize, but it uses additional memory.
Testing
def test_count_battleships():
s = Solution()
board1 = [
["X", ".", ".", "X"],
[".", ".", ".", "X"],
[".", ".", ".", "X"],
]
assert s.countBattleships(board1) == 2
board2 = [["."]]
assert s.countBattleships(board2) == 0
board3 = [["X"]]
assert s.countBattleships(board3) == 1
board4 = [
["X", "X", ".", "X"],
[".", ".", ".", "."],
["X", ".", "X", "X"],
]
assert s.countBattleships(board4) == 4
board5 = [
["X"],
["X"],
["X"],
]
assert s.countBattleships(board5) == 1
print("all tests passed")Test Notes
| Test | Why |
|---|---|
| Standard example | Mixed single and vertical ships |
| Empty board | No ships |
| Single-cell ship | Smallest battleship |
| Multiple separated ships | Checks counting logic |
| Vertical ship | Ensures only top cell is counted |