Count Cells in Overlapping Horizontal and Vertical Substrings
MedPrompt
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.
Example 1:
Input: 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 (colored blue) and once as a vertical substring (colored red), intersecting at one cell (colored purple).
Example 2:
Input: grid = [["c","a","a","a"],["a","a","b","a"],["b","b","a","a"],["a","a","b","a"]], pattern = "aba"
Output: 4
Explanation:
The cells colored above are all part of at least one horizontal and one vertical substring matching the pattern "aba".
Example 3:
Input: grid = [["a"]], pattern = "a"
Output: 1
Constraints:
m == grid.lengthn == grid[i].length1 <= m, n <= 10001 <= m * n <= 1051 <= pattern.length <= m * ngridandpatternconsist of only lowercase English letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the process described in the problem. It checks every possible starting cell in the grid for both horizontal and vertical occurrences of the pattern. For each potential start, it performs a direct character-by-character comparison. This method is straightforward but computationally expensive.
Algorithm
- Initialize
mas the number of rows,nas the number of columns, andLas the length of thepattern. - Create two boolean matrices,
horizontalMatch[m][n]andverticalMatch[m][n], and initialize all their elements tofalse. These will store whether a cell is part of any horizontal or vertical match, respectively. - Horizontal Scan:
- Iterate through each possible starting index
ifrom0tom*n - Lin a conceptually flattened row-major representation of the grid. - For each
i, perform a character-by-character comparison to check if thepatternmatches the grid's content starting from that flattened index. - To get the character at a flattened index
k, use the mappinggrid[k/n][k%n]. - If a match is found starting at
i, iterate fromk = 0toL-1and sethorizontalMatch[(i+k)/n][(i+k)%n]totrue.
- Iterate through each possible starting index
- Vertical Scan:
- Perform a similar scan, but this time for a column-major flattened representation.
- Iterate through each possible starting index
ifrom0tom*n - L. - To get the character at a flattened index
k, use the mappinggrid[k%m][k/m]. - If a match is found, update the corresponding cells in the
verticalMatchmatrix totrue.
- Final Count:
- Initialize a counter
countto0. - Iterate through every cell
(r, c)of the grid. - If
horizontalMatch[r][c]andverticalMatch[r][c]are bothtrue, incrementcount.
- Initialize a counter
- Return
count.
Walkthrough
The brute-force approach involves two main stages. First, we identify all cells that are part of any horizontal substring matching the pattern. Second, we do the same for vertical substrings. We use two separate boolean grids, horizontalMatch and verticalMatch, to mark these cells.
For the horizontal scan, we can imagine the m x n grid being flattened into a single long string of length m * n by concatenating rows. We then iterate through all possible starting positions in this flattened string and check if the pattern matches. If it does, we mark all the corresponding cells in our horizontalMatch grid. A similar process is repeated for the vertical scan, where we imagine the grid flattened column by column.
Finally, we iterate through the grid one last time and count the number of cells (r, c) for which both horizontalMatch[r][c] and verticalMatch[r][c] are true. This count is our final answer.
class Solution { public int countMatchingCells(char[][] grid, String pattern) { int m = grid.length; int n = grid[0].length; int L = pattern.length(); int N = m * n; if (L > N) { return 0; } boolean[][] horizontalMatch = new boolean[m][n]; boolean[][] verticalMatch = new boolean[m][n]; // Horizontal scan for (int i = 0; i <= N - L; i++) { boolean isMatch = true; for (int k = 0; k < L; k++) { int r = (i + k) / n; int c = (i + k) % n; if (grid[r][c] != pattern.charAt(k)) { isMatch = false; break; } } if (isMatch) { for (int k = 0; k < L; k++) { int r = (i + k) / n; int c = (i + k) % n; horizontalMatch[r][c] = true; } } } // Vertical scan for (int i = 0; i <= N - L; i++) { boolean isMatch = true; for (int k = 0; k < L; k++) { int r = (i + k) % m; int c = (i + k) / m; if (grid[r][c] != pattern.charAt(k)) { isMatch = false; break; } } if (isMatch) { for (int k = 0; k < L; k++) { int r = (i + k) % m; int c = (i + k) / m; verticalMatch[r][c] = true; } } } // Count overlapping cells int count = 0; for (int r = 0; r < m; r++) { for (int c = 0; c < n; c++) { if (horizontalMatch[r][c] && verticalMatch[r][c]) { count++; } } } return count; }}Complexity
Time
O((m * n) * L) - Let `N = m * n` and `L = pattern.length()`. For both horizontal and vertical scans, we iterate through `O(N)` possible starting positions. For each position, we perform a check that takes `O(L)` time. This results in a total time complexity of `O(N * L)`.
Space
O(m * n) - We use two boolean matrices of size `m x n` to store the match information.
Trade-offs
Pros
It is simple to understand and implement as it directly follows the problem statement.
Cons
The time complexity is very high, making it impractical for large grids or patterns. It will likely result in a 'Time Limit Exceeded' error on platforms with strict time limits.
A highly efficient approach uses the Knuth-Morris-Pratt (KMP) algorithm for fast pattern searching, combined with a sweep-line technique to efficiently mark all cells covered by matches. This reduces the time complexity from polynomial to linear.
Algorithm
- KMP Pre-computation: Compute the Longest Proper Prefix Suffix (LPS) array for the
pattern. This takesO(L)time. - Find Horizontal Match Starts:
- Use the KMP algorithm to search for the
patternin the row-major flattened grid. Instead of creating the flattened string, access characters on-the-fly viagrid[i/n][i%n]. - Store the starting indices of all found matches in a boolean array
hStartsof sizem*n. This step takesO(m*n).
- Use the KMP algorithm to search for the
- Mark Covered Horizontal Cells:
- Use a sweep-line algorithm to populate the
horizontalMatchboolean matrix. - Initialize an
activeMatchescounter to 0. Iterateifrom0tom*n - 1. - At each
i, decrementactiveMatchesif a match that started ati-Lhas just ended. IncrementactiveMatchesif a new match starts ati. - If
activeMatches > 0, the celliis covered, so markhorizontalMatch[i/n][i%n]as true. This takesO(m*n).
- Use a sweep-line algorithm to populate the
- Find and Mark Vertical Matches: Repeat the previous two steps for the vertical (column-major) traversal, using
grid[i%m][i/m]for character access and populating averticalMatchmatrix. - Final Count: Iterate through the grid and count cells where
horizontalMatch[r][c]andverticalMatch[r][c]are both true. This takesO(m*n).
Walkthrough
This optimal approach improves upon the brute-force method in two key areas: finding matches and marking covered cells.
1. Fast Pattern Searching with KMP:
Instead of a naive O(N*L) search, we use the KMP algorithm. KMP can find all occurrences of a pattern of length L in a text of length N in O(N+L) time. We apply KMP twice: once for the horizontal (row-major) traversal and once for the vertical (column-major) traversal. We don't need to build the flattened strings explicitly; we can simulate access to them. The results are stored in two boolean arrays, hStarts and vStarts, indicating the starting positions of matches.
2. Efficient Marking with Sweep-line:
After finding all match start positions, simply iterating through each match to mark cells would revert to a O(N*L) worst-case complexity. To avoid this, we use a sweep-line algorithm. For the horizontal matches, we iterate through the N cells of the flattened grid, maintaining a counter for activeMatches. This counter tracks how many patterns are currently covering the cell being processed. If the counter is greater than zero, the cell is part of at least one match. This allows us to populate the horizontalMatch and verticalMatch grids in O(N) time each.
Finally, with both horizontalMatch and verticalMatch grids populated, we count the overlapping cells in O(N) time.
class Solution { public int countMatchingCells(char[][] grid, String pattern) { int m = grid.length; int n = grid[0].length; int L = pattern.length(); int N = m * n; if (L > N) { return 0; } boolean[][] horizontalMatch = new boolean[m][n]; boolean[][] verticalMatch = new boolean[m][n]; // Find and mark horizontal matches boolean[] hStarts = findMatchStarts(m, n, L, pattern, true, grid); markCoveredCells(m, n, L, hStarts, horizontalMatch, true); // Find and mark vertical matches boolean[] vStarts = findMatchStarts(m, n, L, pattern, false, grid); markCoveredCells(m, n, L, vStarts, verticalMatch, false); // Count overlapping cells int count = 0; for (int r = 0; r < m; r++) { for (int c = 0; c < n; c++) { if (horizontalMatch[r][c] && verticalMatch[r][c]) { count++; } } } return count; } private boolean[] findMatchStarts(int m, int n, int L, String pattern, boolean isHorizontal, char[][] grid) { int N = m * n; boolean[] starts = new boolean[N]; if (L == 0) return starts; int[] lps = computeLPS(pattern); int j = 0; // pattern index for (int i = 0; i < N; i++) { // text index char textChar = isHorizontal ? grid[i / n][i % n] : grid[i % m][i / m]; while (j > 0 && pattern.charAt(j) != textChar) { j = lps[j - 1]; } if (pattern.charAt(j) == textChar) { j++; } if (j == L) { starts[i - L + 1] = true; j = lps[j - 1]; } } return starts; } private int[] computeLPS(String pattern) { int L = pattern.length(); if (L == 0) return new int[0]; int[] lps = new int[L]; int length = 0; int i = 1; while (i < L) { if (pattern.charAt(i) == pattern.charAt(length)) { length++; lps[i] = length; i++; } else { if (length != 0) { length = lps[length - 1]; } else { lps[i] = 0; i++; } } } return lps; } private void markCoveredCells(int m, int n, int L, boolean[] starts, boolean[][] matchGrid, boolean isHorizontal) { int N = m * n; int activeMatches = 0; for (int i = 0; i < N; i++) { if (i >= L && starts[i - L]) { activeMatches--; } if (starts[i]) { activeMatches++; } if (activeMatches > 0) { int r = isHorizontal ? i / n : i % m; int c = isHorizontal ? i % n : i / m; matchGrid[r][c] = true; } } }}Complexity
Time
O(m * n + L) - Let `N = m * n` and `L = pattern.length()`. The KMP search takes `O(N+L)` and the sweep-line marking takes `O(N)`. This is done for both horizontal and vertical directions. The total time complexity is linear with respect to the size of the grid and the pattern.
Space
O(m * n + L) - We use boolean matrices and start arrays of size `m*n`, and an LPS array of size `L`. Since `L` can be at most `m*n`, this simplifies to `O(m*n)`.
Trade-offs
Pros
Extremely efficient, with an optimal time complexity that handles large inputs easily.
It's a robust solution for string searching-related problems.
Cons
The implementation is more complex, requiring a good understanding of the KMP algorithm and the sweep-line technique.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.