Crossword Grid Validation
Problem
A typical American-style crossword puzzle grid is an N x N matrix with black and white squares, which obeys the following rules:
- Every white square must be part of an "across" word and a "down" word.
- No word can be fewer than three letters long.
- Every white square must be reachable from every other white square.
- The grid is rotationally symmetric (for example, the colors of the top left and bottom right squares must match).
Write a program to determine whether a given matrix qualifies as a crossword grid.
Examples
Example 1
Input:
grid = [
[1, 1, 1],
[1, 0, 1],
[1, 1, 1]
]
(1 = white, 0 = black)
Output: true
Explanation:
- All white squares form valid across and down words of length 3
- Grid is rotationally symmetric around center
- All white squares are connected
- All words are at least 3 letters long
Example 2
Input:
grid = [
[1, 1, 0],
[1, 0, 1],
[0, 1, 1]
]
Output: false
Explanation:
- Grid is not rotationally symmetric
- Top-left is white (1) but bottom-right is white (1) - should match
- Top-right is black (0) but bottom-left is black (0) - this matches
- But when rotated 180°, pattern doesn't match original
Example 3
Input:
grid = [
[1, 1, 1, 1],
[1, 0, 0, 1],
[1, 0, 0, 1],
[1, 1, 1, 1]
]
Output: false
Explanation:
- Rotationally symmetric: ✓
- Contains words shorter than 3 letters: the isolated corner squares form 1-letter "words"
- Middle white squares don't form valid across/down words of length ≥3
Example 4
Input:
grid = [
[1, 1, 1, 0, 1],
[1, 0, 1, 0, 1],
[1, 1, 1, 1, 1],
[1, 0, 1, 0, 1],
[1, 0, 1, 1, 1]
]
Output: true
Explanation:
- Rotationally symmetric: ✓
- All white squares reachable from each other: ✓
- All words are at least 3 letters long: ✓
- Each white square part of valid across and down words: ✓
Example 5
Input:
grid = [
[1, 1],
[1, 1]
]
Output: false
Explanation:
- Although rotationally symmetric and all squares connected
- Words are only 2 letters long, violating minimum length requirement
Solution
Method 1 - Comprehensive Rule Validation
Intuition
We need to validate each rule systematically: check rotational symmetry first (quick elimination), then verify connectivity using DFS/BFS, and finally ensure all words meet the minimum length requirement. Each white square must be part of both horizontal and vertical words of at least 3 letters.
Approach
- Check rotational symmetry by comparing grid[i][j] with grid[n-1-i][n-1-j] for all positions
- Find all white squares and verify connectivity using DFS from any white square
- Extract all horizontal and vertical words from the grid
- Ensure every white square belongs to words of length ≥ 3 in both directions
- Validate that isolated segments don't create invalid short words
Code
C++
class Solution {
private:
vector<vector<int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
bool isValidPosition(int r, int c, int n) {
return r >= 0 && r < n && c >= 0 && c < n;
}
bool checkRotationalSymmetry(vector<vector<int>>& grid) {
int n = grid.size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != grid[n-1-i][n-1-j]) {
return false;
}
}
}
return true;
}
bool checkConnectivity(vector<vector<int>>& grid) {
int n = grid.size();
vector<vector<bool>> visited(n, vector<bool>(n, false));
// Find first white square
int startR = -1, startC = -1;
int whiteCount = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
whiteCount++;
if (startR == -1) {
startR = i;
startC = j;
}
}
}
}
if (whiteCount == 0) return true; // No white squares
// DFS from first white square
function<void(int, int)> dfs = [&](int r, int c) {
visited[r][c] = true;
for (auto& dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (isValidPosition(nr, nc, n) && !visited[nr][nc] && grid[nr][nc] == 1) {
dfs(nr, nc);
}
}
};
dfs(startR, startC);
// Check if all white squares visited
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
return false;
}
}
}
return true;
}
bool checkWordLengths(vector<vector<int>>& grid) {
int n = grid.size();
// Check horizontal words
for (int i = 0; i < n; i++) {
vector<int> wordLengths;
int currentLength = 0;
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
currentLength++;
} else {
if (currentLength > 0) {
wordLengths.push_back(currentLength);
currentLength = 0;
}
}
}
if (currentLength > 0) {
wordLengths.push_back(currentLength);
}
for (int len : wordLengths) {
if (len < 3) return false;
}
}
// Check vertical words
for (int j = 0; j < n; j++) {
vector<int> wordLengths;
int currentLength = 0;
for (int i = 0; i < n; i++) {
if (grid[i][j] == 1) {
currentLength++;
} else {
if (currentLength > 0) {
wordLengths.push_back(currentLength);
currentLength = 0;
}
}
}
if (currentLength > 0) {
wordLengths.push_back(currentLength);
}
for (int len : wordLengths) {
if (len < 3) return false;
}
}
return true;
}
public:
bool isValidCrossword(vector<vector<int>>& grid) {
if (grid.empty()) return false;
int n = grid.size();
if (n < 3) return false; // Minimum size for 3-letter words
// Rule 4: Check rotational symmetry
if (!checkRotationalSymmetry(grid)) {
return false;
}
// Rule 3: Check connectivity
if (!checkConnectivity(grid)) {
return false;
}
// Rule 2: Check word lengths
if (!checkWordLengths(grid)) {
return false;
}
return true;
}
};
Go
func isValidCrossword(grid [][]int) bool {
if len(grid) == 0 {
return false
}
n := len(grid)
if n < 3 {
return false
}
dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
isValidPosition := func(r, c, n int) bool {
return r >= 0 && r < n && c >= 0 && c < n
}
// Check rotational symmetry
checkRotationalSymmetry := func() bool {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] != grid[n-1-i][n-1-j] {
return false
}
}
}
return true
}
// Check connectivity
checkConnectivity := func() bool {
visited := make([][]bool, n)
for i := range visited {
visited[i] = make([]bool, n)
}
startR, startC := -1, -1
whiteCount := 0
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
whiteCount++
if startR == -1 {
startR, startC = i, j
}
}
}
}
if whiteCount == 0 {
return true
}
var dfs func(r, c int)
dfs = func(r, c int) {
visited[r][c] = true
for _, dir := range dirs {
nr, nc := r+dir[0], c+dir[1]
if isValidPosition(nr, nc, n) && !visited[nr][nc] && grid[nr][nc] == 1 {
dfs(nr, nc)
}
}
}
dfs(startR, startC)
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 && !visited[i][j] {
return false
}
}
}
return true
}
// Check word lengths
checkWordLengths := func() bool {
// Check horizontal words
for i := 0; i < n; i++ {
var wordLengths []int
currentLength := 0
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
currentLength++
} else {
if currentLength > 0 {
wordLengths = append(wordLengths, currentLength)
currentLength = 0
}
}
}
if currentLength > 0 {
wordLengths = append(wordLengths, currentLength)
}
for _, length := range wordLengths {
if length < 3 {
return false
}
}
}
// Check vertical words
for j := 0; j < n; j++ {
var wordLengths []int
currentLength := 0
for i := 0; i < n; i++ {
if grid[i][j] == 1 {
currentLength++
} else {
if currentLength > 0 {
wordLengths = append(wordLengths, currentLength)
currentLength = 0
}
}
}
if currentLength > 0 {
wordLengths = append(wordLengths, currentLength)
}
for _, length := range wordLengths {
if length < 3 {
return false
}
}
}
return true
}
// Validate all rules
if !checkRotationalSymmetry() {
return false
}
if !checkConnectivity() {
return false
}
if !checkWordLengths() {
return false
}
return true
}
Java
class Solution {
private int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
private boolean isValidPosition(int r, int c, int n) {
return r >= 0 && r < n && c >= 0 && c < n;
}
private boolean checkRotationalSymmetry(int[][] grid) {
int n = grid.length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != grid[n-1-i][n-1-j]) {
return false;
}
}
}
return true;
}
private boolean checkConnectivity(int[][] grid) {
int n = grid.length;
boolean[][] visited = new boolean[n][n];
int startR = -1, startC = -1;
int whiteCount = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
whiteCount++;
if (startR == -1) {
startR = i;
startC = j;
}
}
}
}
if (whiteCount == 0) return true;
dfs(grid, visited, startR, startC, n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1 && !visited[i][j]) {
return false;
}
}
}
return true;
}
private void dfs(int[][] grid, boolean[][] visited, int r, int c, int n) {
visited[r][c] = true;
for (int[] dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (isValidPosition(nr, nc, n) && !visited[nr][nc] && grid[nr][nc] == 1) {
dfs(grid, visited, nr, nc, n);
}
}
}
private boolean checkWordLengths(int[][] grid) {
int n = grid.length;
// Check horizontal words
for (int i = 0; i < n; i++) {
List<Integer> wordLengths = new ArrayList<>();
int currentLength = 0;
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
currentLength++;
} else {
if (currentLength > 0) {
wordLengths.add(currentLength);
currentLength = 0;
}
}
}
if (currentLength > 0) {
wordLengths.add(currentLength);
}
for (int len : wordLengths) {
if (len < 3) return false;
}
}
// Check vertical words
for (int j = 0; j < n; j++) {
List<Integer> wordLengths = new ArrayList<>();
int currentLength = 0;
for (int i = 0; i < n; i++) {
if (grid[i][j] == 1) {
currentLength++;
} else {
if (currentLength > 0) {
wordLengths.add(currentLength);
currentLength = 0;
}
}
}
if (currentLength > 0) {
wordLengths.add(currentLength);
}
for (int len : wordLengths) {
if (len < 3) return false;
}
}
return true;
}
public boolean isValidCrossword(int[][] grid) {
if (grid == null || grid.length == 0) return false;
int n = grid.length;
if (n < 3) return false;
if (!checkRotationalSymmetry(grid)) {
return false;
}
if (!checkConnectivity(grid)) {
return false;
}
if (!checkWordLengths(grid)) {
return false;
}
return true;
}
}
Python
class Solution:
def __init__(self):
self.dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def _is_valid_position(self, r: int, c: int, n: int) -> bool:
return 0 <= r < n and 0 <= c < n
def _check_rotational_symmetry(self, grid: List[List[int]]) -> bool:
n = len(grid)
for i in range(n):
for j in range(n):
if grid[i][j] != grid[n-1-i][n-1-j]:
return False
return True
def _check_connectivity(self, grid: List[List[int]]) -> bool:
n = len(grid)
visited = [[False] * n for _ in range(n)]
start_r, start_c = -1, -1
white_count = 0
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
white_count += 1
if start_r == -1:
start_r, start_c = i, j
if white_count == 0:
return True
def dfs(r: int, c: int) -> None:
visited[r][c] = True
for dr, dc in self.dirs:
nr, nc = r + dr, c + dc
if (self._is_valid_position(nr, nc, n) and
not visited[nr][nc] and grid[nr][nc] == 1):
dfs(nr, nc)
dfs(start_r, start_c)
for i in range(n):
for j in range(n):
if grid[i][j] == 1 and not visited[i][j]:
return False
return True
def _check_word_lengths(self, grid: List[List[int]]) -> bool:
n = len(grid)
# Check horizontal words
for i in range(n):
word_lengths = []
current_length = 0
for j in range(n):
if grid[i][j] == 1:
current_length += 1
else:
if current_length > 0:
word_lengths.append(current_length)
current_length = 0
if current_length > 0:
word_lengths.append(current_length)
for length in word_lengths:
if length < 3:
return False
# Check vertical words
for j in range(n):
word_lengths = []
current_length = 0
for i in range(n):
if grid[i][j] == 1:
current_length += 1
else:
if current_length > 0:
word_lengths.append(current_length)
current_length = 0
if current_length > 0:
word_lengths.append(current_length)
for length in word_lengths:
if length < 3:
return False
return True
def is_valid_crossword(self, grid: List[List[int]]) -> bool:
if not grid or len(grid) == 0:
return False
n = len(grid)
if n < 3:
return False
if not self._check_rotational_symmetry(grid):
return False
if not self._check_connectivity(grid):
return False
if not self._check_word_lengths(grid):
return False
return True
Complexity
- ⏰ Time complexity:
O(n²), where n is the grid size. Each validation step requires O(n²) time to examine all cells - 🧺 Space complexity:
O(n²)for the visited array during connectivity check and recursion stack for DFS
Method 2 - Optimized Early Termination
Intuition
We can optimize by reordering checks for early termination and combining some validation steps. Since rotational symmetry is quickest to check and most likely to fail, we do it first. We can also combine connectivity and word length validation in a single pass.
Approach
- Quick validation: check minimum grid size and basic constraints
- Fast symmetry check with early termination on first mismatch
- Combined connectivity and word validation in single traversal
- Use iterative DFS to avoid recursion overhead
- Cache word segments during connectivity check for length validation
Code
C++
class Solution {
public:
bool isValidCrosswordOptimized(vector<vector<int>>& grid) {
if (grid.empty() || grid.size() < 3) return false;
int n = grid.size();
// Quick symmetry check with early termination
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != grid[n-1-i][n-1-j]) {
return false;
}
}
}
// Combined connectivity and word length check
vector<vector<bool>> visited(n, vector<bool>(n, false));
vector<pair<int, int>> whiteSquares;
// Collect all white squares
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
whiteSquares.push_back({i, j});
}
}
}
if (whiteSquares.empty()) return true;
// Check connectivity using iterative DFS
stack<pair<int, int>> stk;
stk.push(whiteSquares[0]);
visited[whiteSquares[0].first][whiteSquares[0].second] = true;
int visitedCount = 1;
vector<vector<int>> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
while (!stk.empty()) {
auto [r, c] = stk.top();
stk.pop();
for (auto& dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n &&
!visited[nr][nc] && grid[nr][nc] == 1) {
visited[nr][nc] = true;
stk.push({nr, nc});
visitedCount++;
}
}
}
if (visitedCount != whiteSquares.size()) {
return false;
}
// Check word lengths efficiently
for (int i = 0; i < n; i++) {
int len = 0;
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
len++;
} else {
if (len > 0 && len < 3) return false;
len = 0;
}
}
if (len > 0 && len < 3) return false;
}
for (int j = 0; j < n; j++) {
int len = 0;
for (int i = 0; i < n; i++) {
if (grid[i][j] == 1) {
len++;
} else {
if (len > 0 && len < 3) return false;
len = 0;
}
}
if (len > 0 && len < 3) return false;
}
return true;
}
};
Go
func isValidCrosswordOptimized(grid [][]int) bool {
if len(grid) == 0 || len(grid) < 3 {
return false
}
n := len(grid)
// Quick symmetry check
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] != grid[n-1-i][n-1-j] {
return false
}
}
}
// Collect white squares
var whiteSquares [][2]int
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
whiteSquares = append(whiteSquares, [2]int{i, j})
}
}
}
if len(whiteSquares) == 0 {
return true
}
// Check connectivity with iterative DFS
visited := make([][]bool, n)
for i := range visited {
visited[i] = make([]bool, n)
}
stack := [][2]int{whiteSquares[0]}
visited[whiteSquares[0][0]][whiteSquares[0][1]] = true
visitedCount := 1
dirs := [][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}
for len(stack) > 0 {
curr := stack[len(stack)-1]
stack = stack[:len(stack)-1]
r, c := curr[0], curr[1]
for _, dir := range dirs {
nr, nc := r+dir[0], c+dir[1]
if nr >= 0 && nr < n && nc >= 0 && nc < n &&
!visited[nr][nc] && grid[nr][nc] == 1 {
visited[nr][nc] = true
stack = append(stack, [2]int{nr, nc})
visitedCount++
}
}
}
if visitedCount != len(whiteSquares) {
return false
}
// Check word lengths efficiently
for i := 0; i < n; i++ {
length := 0
for j := 0; j < n; j++ {
if grid[i][j] == 1 {
length++
} else {
if length > 0 && length < 3 {
return false
}
length = 0
}
}
if length > 0 && length < 3 {
return false
}
}
for j := 0; j < n; j++ {
length := 0
for i := 0; i < n; i++ {
if grid[i][j] == 1 {
length++
} else {
if length > 0 && length < 3 {
return false
}
length = 0
}
}
if length > 0 && length < 3 {
return false
}
}
return true
}
Java
class Solution {
public boolean isValidCrosswordOptimized(int[][] grid) {
if (grid == null || grid.length == 0 || grid.length < 3) {
return false;
}
int n = grid.length;
// Quick symmetry check
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] != grid[n-1-i][n-1-j]) {
return false;
}
}
}
// Collect white squares
List<int[]> whiteSquares = new ArrayList<>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
whiteSquares.add(new int[]{i, j});
}
}
}
if (whiteSquares.isEmpty()) return true;
// Check connectivity with iterative DFS
boolean[][] visited = new boolean[n][n];
Stack<int[]> stack = new Stack<>();
int[] start = whiteSquares.get(0);
stack.push(start);
visited[start[0]][start[1]] = true;
int visitedCount = 1;
int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
while (!stack.isEmpty()) {
int[] curr = stack.pop();
int r = curr[0], c = curr[1];
for (int[] dir : dirs) {
int nr = r + dir[0], nc = c + dir[1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n &&
!visited[nr][nc] && grid[nr][nc] == 1) {
visited[nr][nc] = true;
stack.push(new int[]{nr, nc});
visitedCount++;
}
}
}
if (visitedCount != whiteSquares.size()) {
return false;
}
// Check word lengths efficiently
for (int i = 0; i < n; i++) {
int len = 0;
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
len++;
} else {
if (len > 0 && len < 3) return false;
len = 0;
}
}
if (len > 0 && len < 3) return false;
}
for (int j = 0; j < n; j++) {
int len = 0;
for (int i = 0; i < n; i++) {
if (grid[i][j] == 1) {
len++;
} else {
if (len > 0 && len < 3) return false;
len = 0;
}
}
if (len > 0 && len < 3) return false;
}
return true;
}
}
Python
class Solution:
def is_valid_crossword_optimized(self, grid: List[List[int]]) -> bool:
if not grid or len(grid) < 3:
return False
n = len(grid)
# Quick symmetry check
for i in range(n):
for j in range(n):
if grid[i][j] != grid[n-1-i][n-1-j]:
return False
# Collect white squares
white_squares = []
for i in range(n):
for j in range(n):
if grid[i][j] == 1:
white_squares.append((i, j))
if not white_squares:
return True
# Check connectivity with iterative DFS
visited = [[False] * n for _ in range(n)]
stack = [white_squares[0]]
visited[white_squares[0][0]][white_squares[0][1]] = True
visited_count = 1
dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)]
while stack:
r, c = stack.pop()
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if (0 <= nr < n and 0 <= nc < n and
not visited[nr][nc] and grid[nr][nc] == 1):
visited[nr][nc] = True
stack.append((nr, nc))
visited_count += 1
if visited_count != len(white_squares):
return False
# Check word lengths efficiently
for i in range(n):
length = 0
for j in range(n):
if grid[i][j] == 1:
length += 1
else:
if length > 0 and length < 3:
return False
length = 0
if length > 0 and length < 3:
return False
for j in range(n):
length = 0
for i in range(n):
if grid[i][j] == 1:
length += 1
else:
if length > 0 and length < 3:
return False
length = 0
if length > 0 and length < 3:
return False
return True
Complexity
- ⏰ Time complexity:
O(n²), same as Method 1 but with better constant factors due to early termination and reduced function call overhead - 🧺 Space complexity:
O(n²)for visited array and white squares collection, but uses iterative approach to avoid recursion stack
Notes
- Method 1 provides a clear, systematic validation of each crossword rule with separate helper functions
- Method 2 optimizes performance through early termination, iterative algorithms, and combined validation steps
- Both methods handle edge cases like empty grids, small grids, and grids with no white squares
- The rotational symmetry check is the fastest elimination test and should be performed first
- Connectivity verification ensures all white squares form a single connected component
- Word length validation prevents invalid short words that would make the puzzle unsolvable
- The 180-degree rotational symmetry is a key aesthetic and structural requirement for American crosswords
- Real crossword construction involves additional constraints like word quality and theme coherence
- The algorithm can be extended to support rectangular grids or different symmetry patterns
- Consider optimizing further with bit manipulation for very large grids or real-time validation