A clear explanation of checking whether rows and columns read the same using direct index comparison.
Problem Restatement
We are given an array of strings:
wordsReturn True if the strings form a valid word square.
A word square means:
row k == column kfor every valid index k.
The words may have different lengths, so we must be careful with missing characters.
The source statement defines a valid word square as a sequence where the kth row and kth column read the same string, for 0 <= k < max(numRows, numColumns). The constraints are 1 <= words.length <= 500, 1 <= words[i].length <= 500, and each word contains lowercase English letters.
Input and Output
| Item | Meaning |
|---|---|
| Input | A list of strings words |
| Output | True if it forms a valid word square |
| Row | words[i] |
| Column | Characters words[0][i], words[1][i], … where they exist |
| Important issue | Words may have different lengths |
Example function shape:
def validWordSquare(words: list[str]) -> bool:
...Examples
Example 1:
words = ["abcd", "bnrt", "crmy", "dtye"]The answer is:
TrueRows:
abcd
bnrt
crmy
dtyeColumns:
abcd
bnrt
crmy
dtyeEvery row matches the corresponding column.
Example 2:
words = ["abcd", "bnrt", "crm", "dt"]The answer is:
TrueThe rows are:
abcd
bnrt
crm
dtThe columns are:
abcd
bnrt
crm
dtEven though the words have different lengths, the row and column strings still match.
Example 3:
words = ["ball", "area", "read", "lady"]The answer is:
FalseThe third row is:
"read"but the third column is:
"lead"They do not match.
First Thought: Build the Columns
One direct approach is to construct every column string.
For each column index c, collect:
words[0][c], words[1][c], words[2][c], ...when those characters exist.
Then compare each column string with words[c].
This works, but building column strings uses extra memory.
We can check the same condition directly with indices.
Key Insight
The word square condition is the same as matrix symmetry.
For every character at position:
words[i][j]there must be a matching character at the transposed position:
words[j][i]So for every existing words[i][j], we must verify:
- Row
jexists. - Column position
iexists insidewords[j]. - The characters are equal.
If any of these checks fails, the word square is invalid.
Algorithm
Loop through every row index i.
Loop through every character index j in words[i].
For each character words[i][j]:
- If
j >= len(words), returnFalse. - If
i >= len(words[j]), returnFalse. - If
words[i][j] != words[j][i], returnFalse.
If all checks pass, return True.
Correctness
A valid word square requires the ith row and the ith column to read the same string.
That means every character in row i, column j, must match the character in row j, column i.
The algorithm checks exactly this condition for every existing character words[i][j].
If the transposed position does not exist, then the corresponding column is too short or the required row does not exist. In that case, the row and column cannot be equal, so returning False is correct.
If the transposed character exists but differs, then the corresponding row and column read different strings, so returning False is correct.
If the algorithm finishes without finding any missing or mismatched transposed character, then every row-character has a matching column-character. Therefore, every row equals its corresponding column, and the word square is valid.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(T) | We check each character once |
| Space | O(1) | Only loop indices are used |
Here:
| Symbol | Meaning |
|---|---|
T | Total number of characters in all words |
Since there can be up to 500 words and each word can have length up to 500, the worst-case time is O(500 * 500).
Implementation
from typing import List
class Solution:
def validWordSquare(self, words: List[str]) -> bool:
for i, word in enumerate(words):
for j, ch in enumerate(word):
if j >= len(words):
return False
if i >= len(words[j]):
return False
if ch != words[j][i]:
return False
return TrueCode Explanation
We scan each row:
for i, word in enumerate(words):Here i is the row index.
Then we scan each character inside that row:
for j, ch in enumerate(word):Here j is the column index.
The character ch is:
words[i][j]Before comparing it with words[j][i], we check that row j exists:
if j >= len(words):
return FalseThen we check that words[j] is long enough:
if i >= len(words[j]):
return FalseFinally, we compare the two transposed characters:
if ch != words[j][i]:
return FalseIf no check fails, the square is valid:
return TrueTesting
def test_valid_word_square():
s = Solution()
assert s.validWordSquare(["abcd", "bnrt", "crmy", "dtye"]) is True
assert s.validWordSquare(["abcd", "bnrt", "crm", "dt"]) is True
assert s.validWordSquare(["ball", "area", "read", "lady"]) is False
assert s.validWordSquare(["abc", "b"]) is False
assert s.validWordSquare(["a"]) is True
assert s.validWordSquare(["ab", "ba"]) is True
assert s.validWordSquare(["ab", "b"]) is True
assert s.validWordSquare(["abc", "bde", "ce"]) is False
print("all tests passed")Test Notes
| Test | Why |
|---|---|
| Full square | Standard valid case |
| Ragged valid square | Words have different lengths but still match |
| Mismatch | Same size but different row and column |
| Missing transposed character | Detects too-short row |
| Single word | Minimum valid input |
["ab", "ba"] | Small symmetric square |
["ab", "b"] | Small ragged valid square |
| Unequal transposed character | Detects character mismatch |