Taro Logo

Count Cells in Overlapping Horizontal and Vertical Substrings

Medium
Google logo
Google
1 view
Topics:
ArraysStrings

You are given an m x n matrix grid consisting of characters and a string pattern.

A horizontal substring is a contiguous sequence of characters read from left to right. If the end of a row is reached before the substring is complete, it wraps to the first column of the next row and continues as needed. You do not wrap from the bottom row back to the top.

A vertical substring is a contiguous sequence of characters read from top to bottom. If the bottom of a column is reached before the substring is complete, it wraps to the first row of the next column and continues as needed. You do not wrap from the last column back to the first.

Count the number of cells in the matrix that satisfy the following condition:

  • The cell must be part of at least one horizontal substring and at least one vertical substring, where both substrings are equal to the given pattern.

Return the count of these cells.

For example:

grid = [['a','a','c','c'],['b','b','b','c'],['a','a','b','a'],['c','a','a','c'],['a','a','b','a']], pattern = 'abaca'

Output: 1

Explanation: The pattern 'abaca' appears once as a horizontal substring and once as a vertical substring, intersecting at one cell.

grid = [['c','a','a','a'],['a','a','b','a'],['b','b','a','a'],['a','a','b','a']], pattern = 'aba'

Output: 4

Explanation: The cells are all part of at least one horizontal and one vertical substring matching the pattern 'aba'.

grid = [['a']], pattern = 'a'

Output: 1

Solution


Brute Force Approach

  1. Horizontal Search: Iterate through each cell of the grid. Starting from that cell, check if the pattern exists as a horizontal substring (with wrapping). If a match is found, mark all cells in the substring as part of a horizontal match.
  2. Vertical Search: Similarly, iterate through each cell again. From each cell, check if the pattern exists as a vertical substring (with wrapping). If a match is found, mark all cells in the substring as part of a vertical match.
  3. Count Intersections: Finally, iterate through the grid one more time. Count the number of cells that are marked as part of both a horizontal and a vertical match.

Code (Python):

def count_intersections_brute_force(grid, pattern):
    m, n = len(grid), len(grid[0])
    len_pattern = len(pattern)
    
    horizontal_matches = set()
    vertical_matches = set()

    # Helper functions to check for horizontal and vertical matches
    def check_horizontal(row, col):
        substring = ""
        for i in range(len_pattern):
            substring += grid[row][(col + i) % n]
        return substring == pattern

    def check_vertical(row, col):
        substring = ""
        for i in range(len_pattern):
            substring += grid[(row + i) % m][col]
        return substring == pattern

    # Find horizontal matches
    for r in range(m):
        for c in range(n):
            if check_horizontal(r, c):
                for i in range(len_pattern):
                    horizontal_matches.add((r, (c + i) % n))

    # Find vertical matches
    for r in range(m):
        for c in range(n):
            if check_vertical(r, c):
                for i in range(len_pattern):
                    vertical_matches.add(((r + i) % m, c))

    # Count cells in both horizontal and vertical matches
    count = 0
    for r in range(m):
        for c in range(n):
            if (r, c) in horizontal_matches and (r, c) in vertical_matches:
                count += 1

    return count

Time Complexity: O(m * n * len(pattern)), where m is the number of rows, n is the number of columns, and len(pattern) is the length of the pattern string. We iterate over all m*n cells, and for each cell, we potentially iterate len(pattern) times to check for both horizontal and vertical matches.

Space Complexity: O(m * n) in the worst case, to store the horizontal_matches and vertical_matches sets, where each set could contain all cells of the grid.

Optimized Approach

The brute-force solution is inefficient because it repeatedly constructs substrings. An optimized approach can be outlined as follows:

  1. Horizontal Substring Identification: Create a 2D boolean array horizontal_match of size m x n. horizontal_match[i][j] will be True if a horizontal substring matching pattern starts at grid[i][j]. This can be precomputed by checking every possible starting position. The string comparison can be improved using rolling hash to reduce time complexity of string comparison from O(len(pattern)) to O(1). Alternatively, the naive string comparison doesn't hurt the overall time complexity since it is dominated by the iteration over cells.
  2. Vertical Substring Identification: Similarly, create a 2D boolean array vertical_match of size m x n. vertical_match[i][j] will be True if a vertical substring matching pattern starts at grid[i][j]. Precompute as above.
  3. Intersection Count: Iterate through the grid. For each cell grid[i][j], check if it's part of a horizontal substring AND a vertical substring equal to the pattern. If so, increment the count.

To determine if a cell grid[i][j] is part of a horizontal substring, check if there exists k such that grid[i][(j - k + n) % n] exists and horizontal_match[i][(j - k + n) % n] is true and 0 <= k < len(pattern). Do the same for the vertical substring.

Code (Python):

def count_intersections_optimized(grid, pattern):
    m, n = len(grid), len(grid[0])
    len_pattern = len(pattern)

    horizontal_match = [[False] * n for _ in range(m)]
    vertical_match = [[False] * m for _ in range(n)]

    # Find horizontal matches
    for r in range(m):
        for c in range(n):
            if c + len_pattern <= n:
                substring = "".join(grid[r][c + i] for i in range(len_pattern))
                if substring == pattern:
                    horizontal_match[r][c] = True

    # Find vertical matches
    for c in range(n):
        for r in range(m):
            if r + len_pattern <= m:
                substring = "".join(grid[r + i][c] for i in range(len_pattern))
                if substring == pattern:
                    vertical_match[c][r] = True

    # Count intersections
    count = 0
    for r in range(m):
        for c in range(n):
            is_horizontal = False
            for k in range(len_pattern):
                prev_c = (c - k + n) % n
                if prev_c + len_pattern <= n and horizontal_match[r][prev_c]:
                    is_horizontal = True
                    break

            is_vertical = False
            for k in range(len_pattern):
                prev_r = (r - k + m) % m
                if prev_r + len_pattern <= m and vertical_match[c][prev_r]:
                    is_vertical = True
                    break

            if is_horizontal and is_vertical:
                count += 1

    return count

Time Complexity: O(m * n * len(pattern)) if using naive string comparision for horizontal and vertical matching. O(m * n) if using rolling hash for horizontal and vertical matching.

Space Complexity: O(m * n) to store the horizontal_match and vertical_match boolean arrays.

Edge Cases

  • Empty grid: m = 0 or n = 0. Return 0.
  • Empty pattern: len(pattern) = 0. Return 0 (no possible matches).
  • Pattern longer than the grid's row or column: It's impossible to find a horizontal or vertical match in such cases, so you'd likely return 0.
  • Grid and pattern consisting of a single character. This needs to be handled correctly.