Taro Logo

Battleships in a Board

Medium
Meta logo
Meta
3 views
Topics:
Arrays

Given an m x n matrix board where each cell is a battleship 'X' or empty '.', return the number of the battleships on 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).

For example:

board = [['X','.','.','X'], ['.','.','.','X'], ['.','.','.','X']]

In this case, the output should be 2.

Another example:

board = [['.']]

In this case, the output should be 0.

Constraints:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j] is either '.' or 'X'.

Could you do it in one-pass, using only O(1) extra memory and without modifying the values board?

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What characters will represent a battleship and an empty cell? Can I assume that only 'X' represents a battleship and '.' represents an empty cell?
  2. Can battleships wrap around the board edges or are they guaranteed to be fully contained within the grid?
  3. Will the battleships be guaranteed to be separated by at least one horizontal or vertical cell, or can they be adjacent diagonally?
  4. Is the input board guaranteed to be rectangular, or could it be ragged?
  5. What are the maximum dimensions of the board? Is there a size limit that I should consider for memory allocation?

Brute Force Solution

Approach

We're given a grid where some cells contain parts of battleships. The brute force method involves carefully examining each cell to find and count these battleships. We need to be thorough and avoid counting the same ship multiple times.

Here's how the algorithm would work step-by-step:

  1. Start at the very first cell in the grid.
  2. Check if that cell contains part of a battleship.
  3. If it does, then we've potentially found a new battleship.
  4. To confirm, we explore all adjacent cells (to the right and below) to see if they also contain parts of the same battleship. We only explore right and down to avoid double counting.
  5. Once we've explored the entire battleship and marked all its parts, we increment our battleship count.
  6. Continue this process, moving cell by cell across the entire grid, checking each cell for a potential battleship.
  7. If a cell contains part of a battleship that we've already counted, we simply skip it.
  8. After checking every single cell in the grid, the final battleship count is the total number of distinct battleships found.

Code Implementation

def count_battleships_brute_force(board):
    if not board:
        return 0

    number_of_rows = len(board)
    number_of_columns = len(board[0])
    battleship_count = 0

    for row_index in range(number_of_rows):
        for column_index in range(number_of_columns):
            if board[row_index][column_index] == 'X':
                # Found a potential battleship

                battleship_count += 1
                sink_battleship(board, row_index, column_index, number_of_rows, number_of_columns)

    return battleship_count

def sink_battleship(board, row_index, column_index, number_of_rows, number_of_columns):
    # Mark the current cell as visited to avoid double-counting.
    board[row_index][column_index] = '.'

    # Explore right
    if column_index + 1 < number_of_columns and board[row_index][column_index + 1] == 'X':
        sink_battleship(board, row_index, column_index + 1, number_of_rows, number_of_columns)

    # Explore down
    if row_index + 1 < number_of_rows and board[row_index + 1][column_index] == 'X':
        # Explore down if there is a ship
        sink_battleship(board, row_index + 1, column_index, number_of_rows, number_of_columns)

Big(O) Analysis

Time Complexity
O(m*n)The algorithm iterates through each cell of the m x n grid. For each cell containing a battleship part 'X', it explores the connected components of the battleship by only checking right and down. In the worst-case scenario, each cell might be visited once while exploring a battleship, and we visit each cell in the grid at least once. Thus, the time complexity is proportional to the number of cells in the grid, leading to O(m*n) where m and n are the dimensions of the grid.
Space Complexity
O(1)The provided solution involves iterating through the grid and potentially marking cells of a battleship as visited implicitly by modifying the input. There is no auxiliary data structure mentioned that grows with the input grid size N (where N is the total number of cells in the grid, rows * cols). The battleship count is a single integer. Thus, the space complexity is constant.

Optimal Solution

Approach

The goal is to count the number of battleships in a given board. The key idea is to only count the 'head' of each battleship to avoid overcounting and ensure efficiency.

Here's how the algorithm would work step-by-step:

  1. Go through each square in the board, one by one.
  2. For each square, check if it contains a part of a battleship.
  3. If it's a battleship part, check if it's the 'head' of the battleship. A square is a 'head' if the squares immediately above it and to its left are not also battleship parts.
  4. If it's the 'head', then increment the battleship count.
  5. Continue until every square is checked, and the final count is the number of battleships.

Code Implementation

def count_battleships(board):
    number_of_rows = len(board)
    number_of_columns = len(board[0]) if number_of_rows > 0 else 0
    battleship_count = 0

    for row_index in range(number_of_rows):
        for column_index in range(number_of_columns):
            if board[row_index][column_index] == 'X':
                # Check if current cell is the 'head' of a battleship.
                if (row_index > 0 and board[row_index - 1][column_index] == 'X'):
                    continue

                if (column_index > 0 and board[row_index][column_index - 1] == 'X'):
                    continue

                # Increment battleship_count if it's the 'head'.
                battleship_count += 1

    return battleship_count

Big(O) Analysis

Time Complexity
O(m*n)The algorithm iterates through each cell in the board. Assuming the board has m rows and n columns, the algorithm visits each of the m*n cells exactly once. For each cell, it performs a constant number of checks (checking the cell above and to the left). Therefore, the time complexity is proportional to the number of cells in the board which is m*n. Hence the overall time complexity is O(m*n).
Space Complexity
O(1)The algorithm iterates through the board and uses a constant number of variables to store the current row and column indices, and the battleship count. No auxiliary data structures such as lists, sets, or maps are created. Therefore, the extra space used by the algorithm remains constant irrespective of the board size (number of rows and columns), making the space complexity O(1).

Edge Cases

CaseHow to Handle
Null or empty boardReturn 0 if the board is null or has zero rows/columns.
Board with zero width or zero heightReturn 0 because a battleship cannot exist without at least one cell.
Board containing only '.' charactersReturn 0 as there are no battleships.
Board containing only 'X' characters representing a single large battleshipCount as one battleship when no adjacent 'X' are present (edge case for naive counting).
Battleship segments are not connected horizontally or vertically (e.g., diagonal 'X's)Only count connected horizontal/vertical segments as part of a battleship, ignoring diagonals.
Invalid board where battleships touch each otherThe problem statement implicitly assumes valid boards so this edge case doesn't need to be handled specifically beyond incorrect counts.
Board with a single row or single columnHandle by iterating through the row or column and counting battleships correctly.
Integer overflow if board size is extremely largeEnsure data types (e.g., counters) are large enough to handle potential board sizes; if the dimensions are constrained, no special action is needed.