422. Valid Word Square 🔒
Problem Description
You are given an array of strings words
. Your task is to determine if these strings form a valid word square.
A word square is formed when the strings are arranged as a square grid where:
- The
k
-th row reads the same as thek
-th column - This must be true for all valid indices
k
where0 <= k < max(numRows, numColumns)
In other words, if you write the words one below another to form a grid, reading horizontally (left to right) at position k
should give you the exact same string as reading vertically (top to bottom) at position k
.
For example, if we have words ["abcd", "bnrt", "crmy", "dtye"]
, they form a valid word square because:
- Row 0: "abcd", Column 0: "abcd" (reading first character of each word)
- Row 1: "bnrt", Column 1: "bnrt" (reading second character of each word)
- Row 2: "crmy", Column 2: "crmy" (reading third character of each word)
- Row 3: "dtye", Column 3: "dtye" (reading fourth character of each word)
The solution checks this by iterating through each character position (i, j)
in the words array. For each character at words[i][j]
, it verifies that:
- The corresponding position
words[j][i]
exists (not out of bounds) - The characters match:
words[i][j] == words[j][i]
If any position violates these conditions, the function returns false
. If all positions satisfy the word square property, it returns true
.
Intuition
The key insight is recognizing that a word square is essentially checking if a matrix is symmetric along its main diagonal. When we arrange the words as rows in a grid, each character at position (i, j)
must equal the character at position (j, i)
.
Think of it like looking at a mirror reflection across the diagonal - what appears in row i
, column j
must be exactly the same as what appears in row j
, column i
.
We arrive at this approach by understanding that:
- Each word represents a row in our conceptual grid
- The
j
-th character of thei
-th word is at position(i, j)
in this grid - For a valid word square, this character must match the
i
-th character of thej
-th word (position(j, i)
)
The challenge is that our "grid" might not be a perfect square - words can have different lengths, and we might have different numbers of words than characters in each word. This means we need to be careful about boundary checking.
When checking words[i][j]
, we must ensure:
j < len(words)
- the column indexj
must correspond to an existing wordi < len(words[j])
- the row indexi
must be within the length of word at positionj
- If either condition fails, we're trying to compare with a non-existent character, so it can't form a valid word square
This leads us to iterate through each character of each word and verify the symmetry property while handling the boundary cases. If we find any mismatch or out-of-bounds access, we immediately know it's not a valid word square.
Solution Approach
The solution uses a straightforward iterative approach to verify the word square property. Let's walk through the implementation step by step:
-
Get the number of words: We store
m = len(words)
to know how many words (rows) we have in our array. -
Iterate through each word with its index: We use
enumerate(words)
to get both the word and its row indexi
. -
For each character in the current word: We iterate through each character
c
at positionj
in wordi
. This represents checking element at position(i, j)
in our conceptual grid. -
Perform three critical checks for each character position
(i, j)
:- Column bounds check:
j >= m
- If the column indexj
exceeds the number of words, thenwords[j]
doesn't exist, so we can't form a valid square. - Row bounds check:
i >= len(words[j])
- If we try to accesswords[j][i]
but wordj
doesn't have enough characters (its length is less than or equal toi
), the position is out of bounds. - Character match check:
c != words[j][i]
- The actual comparison to verify if the character at(i, j)
matches the character at(j, i)
.
- Column bounds check:
-
Early termination: If any of these three conditions are true, we immediately return
False
as the word square property is violated. -
Success case: If we complete all iterations without finding any violations, we return
True
.
The algorithm has a time complexity of O(n × m)
where n
is the number of words and m
is the maximum length of any word, as we potentially check each character once. The space complexity is O(1)
as we only use a constant amount of extra space for variables.
This approach efficiently validates the symmetric property required for a word square while handling the edge cases of non-square grids formed by words of varying lengths.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with words = ["ball", "area", "lead", "lady"]
.
First, let's visualize this as a grid:
b a l l a r e a l e a d l a d y
Now we'll check if this forms a valid word square by verifying that each row matches its corresponding column.
Step 1: Check word 0 ("ball")
- Position (0,0): 'b' should equal words[0][0] = 'b' ✓
- Position (0,1): 'a' should equal words[1][0] = 'a' ✓
- Position (0,2): 'l' should equal words[2][0] = 'l' ✓
- Position (0,3): 'l' should equal words[3][0] = 'l' ✓
Step 2: Check word 1 ("area")
- Position (1,0): 'a' should equal words[0][1] = 'a' ✓
- Position (1,1): 'r' should equal words[1][1] = 'r' ✓
- Position (1,2): 'e' should equal words[2][1] = 'e' ✓
- Position (1,3): 'a' should equal words[3][1] = 'a' ✓
Step 3: Check word 2 ("lead")
- Position (2,0): 'l' should equal words[0][2] = 'l' ✓
- Position (2,1): 'e' should equal words[1][2] = 'e' ✓
- Position (2,2): 'a' should equal words[2][2] = 'a' ✓
- Position (2,3): 'd' should equal words[3][2] = 'd' ✓
Step 4: Check word 3 ("lady")
- Position (3,0): 'l' should equal words[0][3] = 'l' ✓
- Position (3,1): 'a' should equal words[1][3] = 'a' ✓
- Position (3,2): 'd' should equal words[2][3] = 'd' ✓
- Position (3,3): 'y' should equal words[3][3] = 'y' ✓
All checks pass! This forms a valid word square because:
- Row 0 "ball" = Column 0 "ball" (reading b,a,l,l vertically)
- Row 1 "area" = Column 1 "area" (reading a,r,e,a vertically)
- Row 2 "lead" = Column 2 "lead" (reading l,e,a,d vertically)
- Row 3 "lady" = Column 3 "lady" (reading l,a,d,y vertically)
Counter-example: If we had words = ["abc", "bde", "cef"]
, this would fail at position (0,2) because we'd need words[2][0] but "cef" starts with 'c', not matching the 'c' at position (0,2) in "abc".
Solution Implementation
1class Solution:
2 def validWordSquare(self, words: List[str]) -> bool:
3 # Get the number of words (rows in the word square)
4 num_words = len(words)
5
6 # Iterate through each word and its index
7 for row_idx, word in enumerate(words):
8 # Iterate through each character and its position in the current word
9 for col_idx, char in enumerate(word):
10 # Check three conditions for a valid word square:
11 # 1. Column index must not exceed the number of words (out of bounds check)
12 # 2. Row index must not exceed the length of the word at column index (symmetric bounds check)
13 # 3. Character at position (row, col) must equal character at position (col, row)
14 if (col_idx >= num_words or
15 row_idx >= len(words[col_idx]) or
16 char != words[col_idx][row_idx]):
17 return False
18
19 # All characters satisfy the word square property
20 return True
21
1class Solution {
2 /**
3 * Validates if the given list of words forms a valid word square.
4 * A valid word square is when the k-th row and column read the same string.
5 *
6 * @param words List of strings representing the word square
7 * @return true if words form a valid word square, false otherwise
8 */
9 public boolean validWordSquare(List<String> words) {
10 // Get the number of rows in the word square
11 int numRows = words.size();
12
13 // Iterate through each row
14 for (int rowIndex = 0; rowIndex < numRows; ++rowIndex) {
15 // Get the length of the current row (number of columns in this row)
16 int rowLength = words.get(rowIndex).length();
17
18 // Check each character in the current row
19 for (int colIndex = 0; colIndex < rowLength; ++colIndex) {
20 // Check if the corresponding column exists (colIndex should be within bounds)
21 // or if the corresponding row in that column is too short
22 if (colIndex >= numRows || rowIndex >= words.get(colIndex).length()) {
23 return false;
24 }
25
26 // Check if the character at position (rowIndex, colIndex) matches
27 // the character at position (colIndex, rowIndex)
28 if (words.get(rowIndex).charAt(colIndex) != words.get(colIndex).charAt(rowIndex)) {
29 return false;
30 }
31 }
32 }
33
34 // All checks passed, it's a valid word square
35 return true;
36 }
37}
38
1class Solution {
2public:
3 bool validWordSquare(vector<string>& words) {
4 // Get the number of words (rows in the matrix)
5 int numRows = words.size();
6
7 // Iterate through each word (row)
8 for (int row = 0; row < numRows; ++row) {
9 // Get the length of current word (number of columns in this row)
10 int numCols = words[row].size();
11
12 // Check each character in the current word
13 for (int col = 0; col < numCols; ++col) {
14 // Check if the transpose position is valid and characters match
15 // Three conditions must be satisfied:
16 // 1. Column index must not exceed total number of rows
17 // 2. Row index must not exceed the length of the word at column index
18 // 3. Character at (row, col) must equal character at (col, row)
19 if (col >= numRows ||
20 row >= words[col].size() ||
21 words[row][col] != words[col][row]) {
22 return false;
23 }
24 }
25 }
26
27 // All characters match their transpose positions
28 return true;
29 }
30};
31
1/**
2 * Checks if a list of words forms a valid word square.
3 * A valid word square is when the k-th row and column read the same string,
4 * where 0 ≤ k < max(numRows, numColumns).
5 *
6 * @param words - Array of strings to check
7 * @returns true if the words form a valid word square, false otherwise
8 */
9function validWordSquare(words: string[]): boolean {
10 // Get the number of rows (words)
11 const rowCount: number = words.length;
12
13 // Iterate through each row
14 for (let rowIndex: number = 0; rowIndex < rowCount; rowIndex++) {
15 // Get the length of the current word (number of columns in this row)
16 const columnCount: number = words[rowIndex].length;
17
18 // Check each character in the current row
19 for (let columnIndex: number = 0; columnIndex < columnCount; columnIndex++) {
20 // Check if:
21 // 1. Column index exceeds total number of rows
22 // 2. Row index exceeds the length of the word at column index
23 // 3. Character at (row, col) doesn't match character at (col, row)
24 if (columnIndex >= rowCount ||
25 rowIndex >= words[columnIndex].length ||
26 words[rowIndex][columnIndex] !== words[columnIndex][rowIndex]) {
27 return false;
28 }
29 }
30 }
31
32 // All checks passed, it's a valid word square
33 return true;
34}
35
Time and Space Complexity
The time complexity is O(n²)
, where n
is the total number of characters across all words. This can also be expressed as O(m × k)
where m
is the number of words and k
is the maximum length of any word. In the worst case where we have a valid word square with m
words each of length m
, the complexity becomes O(m²)
. The algorithm iterates through each character in each word exactly once, and for each character, it performs constant-time operations (index checking and character comparison).
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for the loop variables i
, j
, w
, and c
, regardless of the input size. No additional data structures are created that scale with the input.
Common Pitfalls
1. Assuming Square Matrix Without Validation
One of the most common mistakes is assuming that the input forms a perfect square matrix (n×n grid) where all words have the same length equal to the number of words. This leads to index out of bounds errors when words have different lengths.
Incorrect Approach:
def validWordSquare(self, words: List[str]) -> bool:
n = len(words)
for i in range(n):
for j in range(n): # Assumes all words have length n
if words[i][j] != words[j][i]: # Can crash here!
return False
return True
Why it fails: If words = ["abc", "b"]
, accessing words[1][1]
or words[1][2]
will cause an IndexError.
2. Incorrect Order of Boundary Checks
Another pitfall is checking character equality before verifying bounds, which can lead to runtime errors.
Incorrect Approach:
def validWordSquare(self, words: List[str]) -> bool:
for i, word in enumerate(words):
for j, c in enumerate(word):
# Checking character match before bounds - WRONG!
if c != words[j][i]: # Can crash if words[j] doesn't exist
return False
if j >= len(words) or i >= len(words[j]):
return False
return True
3. Missing Asymmetric Length Validation
Some implementations forget to check if the transpose position exists when the matrix is not square.
Incorrect Approach:
def validWordSquare(self, words: List[str]) -> bool:
for i in range(len(words)):
word = words[i]
for j in range(len(word)):
# Only checking if words[j] exists, not if words[j][i] exists
if j < len(words) and word[j] != words[j][i]:
return False
return True
Solution to Avoid These Pitfalls:
The correct approach must:
- Check column bounds first (
j >= num_words
) - Then check row bounds (
i >= len(words[j])
) - Only then perform character comparison (
char != words[j][i]
)
This order ensures we never access an invalid index. The key insight is treating the word array as a potentially non-square, ragged 2D array where careful boundary checking is essential before any character access.
How does merge sort divide the problem into subproblems?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!