如何优化字符矩阵构造指定字符串的解法时间复杂度?
Great question! Your naive DFS approach does hit exponential time complexity because it re-calculates identical subproblems repeatedly, especially as the matrix or string length grows. Let’s walk through practical optimizations to speed this up:
1. Preprocess Character Positions
First, cut down on unnecessary initial searches by mapping each character to its coordinates in the matrix. Instead of iterating every cell in the matrix to find starting points, only check cells that match the first character of your target string.
For example:
from collections import defaultdict char_map = defaultdict(list) rows, cols = len(matrix), len(matrix[0]) for i in range(rows): for j in range(cols): char_map[matrix[i][j]].append((i, j))
Now, your initial loop only needs to iterate over char_map[str[0]] instead of all rows*cols cells. You can also use this map to quickly find valid next positions for subsequent characters in the string, avoiding checking all 8 directions when the next character doesn’t match.
2. Prune Invalid Paths Early
Add these pruning checks to your DFS to stop unnecessary recursive calls before they even start:
- Character Mismatch: If the current cell’s character doesn’t match the current position in the string, return 0 immediately.
- End of String: If you’ve reached the last character of the string, return 1 (this is a valid path).
- Insufficient Remaining Cells: Calculate how many characters are left to build, and how many unvisited cells are still available. If you need more characters than available cells, return 0—this path can’t possibly complete the string.
- Boundary & Visitation Checks: Before recursing to a neighboring cell, verify it’s within matrix bounds, not already visited, and matches the next character in the string.
3. Memoization (for Small Matrices)
If your matrix is small (e.g., total cells ≤ 20), you can use bitmasking to represent visited cells and cache results of subproblems.
- Map each cell
(i,j)to a unique integer index:idx = i * cols + j. - Use a bitmask (integer) where the
idx-th bit is 1 if the cell is visited. - Cache the result of
dfs(i, j, current_pos, mask)using a hash map or 4-dimensional array. This way, if you encounter the same cell, same string position, and same visited set again, you can return the cached value instead of re-computing.
Here’s a rough example of how this modified DFS might look:
memo = {} rows, cols = len(matrix), len(matrix[0]) target = "fit" def dfs(i, j, pos, mask): if (i, j, pos, mask) in memo: return memo[(i, j, pos, mask)] if matrix[i][j] != target[pos]: memo[(i, j, pos, mask)] = 0 return 0 if pos == len(target) - 1: memo[(i, j, pos, mask)] = 1 return 1 total = 0 # Check all 8 directions (skip current cell) for dx in [-1, 0, 1]: for dy in [-1, 0, 1]: if dx == 0 and dy == 0: continue x, y = i + dx, j + dy if 0 <= x < rows and 0 <= y < cols: idx = x * cols + y if not (mask & (1 << idx)): total += dfs(x, y, pos + 1, mask | (1 << idx)) memo[(i, j, pos, mask)] = total return total
4. Dynamic Programming (Backward Approach)
For larger matrices where bitmasking isn’t feasible, you can use a backward DP approach:
- Define
dp[k][i][j]as the number of valid paths starting at cell(i,j)that build the substring from indexkto the end of the target string. - Initialization: For the last character of the string (
k = len(target)-1), setdp[k][i][j] = 1ifmatrix[i][j] == target[k], else 0. - Recurrence: For
k < len(target)-1, ifmatrix[i][j] != target[k], setdp[k][i][j] = 0. Otherwise, sum the values ofdp[k+1][x][y]for all valid 8-direction neighbors(x,y)that haven’t been used in the path.
To handle the "no repeated cells" constraint, combine this with backtracking—starting from the end of the string can reduce redundant calculations compared to forward DFS.
内容的提问来源于stack exchange,提问作者tusharRawat

