title: Battleships in a Board
slug: battleships-in-a-board
difficulty: medium
leetcode_id: 419
leetcode_url: https://leetcode.com/problems/battleships-in-a-board/
categories:
  - arrays
  - graphs
patterns:
  - matrix-traversal

description: |
  Given an `m x n` matrix `board` where each cell is a battleship `'X'` or empty `'.'`, return *the number of **battleships** on the board*.

  **Battleships** can only be placed horizontally or vertically on `board`. In other words, they can only be made of the shape `1 x k` (1 row, `k` columns) or `k x 1` (`k` rows, 1 column), where `k` can be of any size. At least one horizontal or vertical cell separates between two battleships (i.e., there are no adjacent battleships).

constraints: |
  - `m == board.length`
  - `n == board[i].length`
  - `1 <= m, n <= 200`
  - `board[i][j]` is either `'.'` or `'X'`

examples:
  - input: 'board = [["X",".",".","X"],[".",".",".","X"],[".",".",".","X"]]'
    output: "2"
    explanation: "There are two battleships: one horizontal at position (0,0) and one vertical spanning positions (0,3), (1,3), (2,3)."
  - input: 'board = [["."]]'
    output: "0"
    explanation: "The board contains no battleships, only empty cells."

explanation:
  intuition: |
    Imagine you're scanning a radar display looking for ships. Each ship appears as a contiguous line of `'X'` marks, either horizontal or vertical. Your task is to count how many distinct ships are present.

    The **key insight** is that you don't need to trace the entire shape of each battleship. Instead, you only need to identify the **"head"** of each ship — the top-left cell where a battleship begins.

    Think of it like this: as you scan the grid from left to right, top to bottom (like reading a book), the first `'X'` you encounter for any battleship is always its top-left corner. Every other `'X'` in that ship will either be below or to the right of this starting point.

    So instead of asking "is this an `'X'`?", ask "is this the **start** of a new battleship?" A cell is the start of a new battleship if:
    - It contains `'X'`
    - There's no `'X'` directly above it (otherwise it's part of a vertical ship that started earlier)
    - There's no `'X'` directly to its left (otherwise it's part of a horizontal ship that started earlier)

    This elegant observation lets us count battleships in a single pass without any extra data structures or recursion.

  approach: |
    We solve this using a **Single Pass (Count Heads)** approach:

    **Step 1: Initialise the counter**

    - `count`: Set to `0` to track the number of battleships found

    &nbsp;

    **Step 2: Iterate through each cell**

    - Traverse the grid row by row, column by column (standard left-to-right, top-to-bottom order)
    - For each cell at position `(i, j)`, check if it's the "head" of a battleship

    &nbsp;

    **Step 3: Identify battleship heads**

    - Skip if `board[i][j] == '.'` (empty cell)
    - Skip if `i > 0` and `board[i-1][j] == 'X'` (this cell is part of a vertical ship that started above)
    - Skip if `j > 0` and `board[i][j-1] == 'X'` (this cell is part of a horizontal ship that started to the left)
    - If none of the above, this is a new battleship head — increment `count`

    &nbsp;

    **Step 4: Return the result**

    - Return `count` after processing all cells

    &nbsp;

    This approach works because each battleship has exactly one "head" cell (its top-left corner), and we count each head exactly once.

  common_pitfalls:
    - title: Using DFS/BFS for Each Ship
      description: |
        A natural instinct is to use flood-fill (DFS or BFS) to explore each battleship when you encounter an `'X'`:
        - Mark all connected `'X'` cells as visited
        - Increment counter after exploring the entire ship

        While this works correctly, it's **overkill** for this problem. The problem statement guarantees that battleships are always straight lines with no adjacency, so you don't need to trace their full extent.

        More importantly, DFS/BFS approaches typically require either:
        - O(m*n) extra space for a visited array, or
        - Modifying the board to mark visited cells

        The follow-up explicitly asks for O(1) space without modification.
      wrong_approach: "DFS/BFS flood-fill with visited tracking"
      correct_approach: "Count only the top-left head of each ship"

    - title: Counting Every X Cell
      description: |
        Simply counting all `'X'` cells gives the wrong answer because a single battleship can span multiple cells.

        For example, with a 3-cell vertical ship, counting all `'X'` cells would report 3 instead of 1.
      wrong_approach: "Increment counter for every 'X' cell"
      correct_approach: "Only count cells that are battleship heads"

    - title: Forgetting Boundary Checks
      description: |
        When checking the cell above (`board[i-1][j]`) or to the left (`board[i][j-1]`), you must ensure you're not accessing invalid indices.

        - Only check `board[i-1][j]` when `i > 0`
        - Only check `board[i][j-1]` when `j > 0`

        Failing to add these guards causes an index-out-of-bounds error on the first row or column.
      wrong_approach: "Check neighbors without boundary validation"
      correct_approach: "Guard neighbor checks with i > 0 and j > 0"

  key_takeaways:
    - "**Counting heads pattern**: When objects span multiple cells, identify a unique 'anchor' point (like the top-left corner) to count each object exactly once"
    - "**Leverage problem constraints**: The guarantee of no adjacent ships and only horizontal/vertical placement enables the O(1) space solution"
    - "**Single-pass matrix traversal**: Scanning in reading order (left-to-right, top-to-bottom) naturally processes heads before their continuations"
    - "**Question the obvious approach**: DFS/BFS is often the go-to for connected components, but simpler patterns exist for constrained inputs"

  time_complexity: "O(m * n). We visit each cell exactly once, performing O(1) work per cell."
  space_complexity: "O(1). We only use a single counter variable, regardless of the board size."

solutions:
  - approach_name: Single Pass (Count Heads)
    is_optimal: true
    code: |
      def count_battleships(board: list[list[str]]) -> int:
          # Counter for battleship heads
          count = 0
          m, n = len(board), len(board[0])

          for i in range(m):
              for j in range(n):
                  # Skip empty cells
                  if board[i][j] == '.':
                      continue

                  # Skip if this is a continuation of a vertical ship
                  if i > 0 and board[i - 1][j] == 'X':
                      continue

                  # Skip if this is a continuation of a horizontal ship
                  if j > 0 and board[i][j - 1] == 'X':
                      continue

                  # This is a battleship head — count it
                  count += 1

          return count
    explanation: |
      **Time Complexity:** O(m * n) — Single pass through all cells.

      **Space Complexity:** O(1) — Only a counter variable is used.

      We scan each cell once and only count `'X'` cells that have no `'X'` neighbor above or to the left. Since we traverse top-to-bottom and left-to-right, such cells are exactly the top-left starting points of each battleship.

  - approach_name: DFS Flood Fill
    is_optimal: false
    code: |
      def count_battleships(board: list[list[str]]) -> int:
          m, n = len(board), len(board[0])
          visited = [[False] * n for _ in range(m)]
          count = 0

          def dfs(i: int, j: int) -> None:
              # Mark current cell as visited
              visited[i][j] = True

              # Explore all 4 directions
              for di, dj in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
                  ni, nj = i + di, j + dj
                  # Check bounds, if unvisited, and if part of ship
                  if 0 <= ni < m and 0 <= nj < n:
                      if not visited[ni][nj] and board[ni][nj] == 'X':
                          dfs(ni, nj)

          for i in range(m):
              for j in range(n):
                  # Found an unvisited battleship cell
                  if board[i][j] == 'X' and not visited[i][j]:
                      dfs(i, j)  # Mark entire ship as visited
                      count += 1  # Count this ship

          return count
    explanation: |
      **Time Complexity:** O(m * n) — Each cell is visited at most once by DFS.

      **Space Complexity:** O(m * n) — The visited array plus recursion stack.

      This approach uses standard flood-fill DFS to explore each battleship completely when first encountered. While correct, it uses extra space for the visited array and is more complex than necessary given the problem's constraints. Included to show the classic connected-components approach.
