A clear explanation of Longest Line of Consecutive One in Matrix using dynamic programming over four directions.
Problem Restatement
We are given a binary matrix mat.
Each cell contains either 0 or 1.
We need to find the length of the longest line of consecutive 1 values.
The line may go in one of four directions:
| Direction | Meaning |
|---|---|
| Horizontal | Left to right |
| Vertical | Top to bottom |
| Diagonal | Top-left to bottom-right |
| Anti-diagonal | Top-right to bottom-left |
Return the maximum length among all such lines.
The original problem states that the number of elements in the matrix does not exceed 10000.
Input and Output
| Item | Meaning |
|---|---|
| Input | A binary matrix mat |
| Output | Length of the longest consecutive line of 1 values |
| Allowed directions | Horizontal, vertical, diagonal, anti-diagonal |
| Cell values | Only 0 and 1 |
Example function shape:
def longestLine(mat: list[list[int]]) -> int:
...Examples
Example:
mat = [
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 1],
]The longest line has length 3.
One such line is the diagonal:
mat[0][1] = 1
mat[1][2] = 1
mat[2][3] = 1So the answer is:
3First Thought: Start From Every Cell
A direct solution is to start from every cell containing 1.
From that cell, try walking in all four directions and count how many consecutive 1 values appear.
This works, but it repeats many checks.
For example, in a long horizontal line of 1 values, starting from each cell recounts much of the same line again.
We need to reuse previous counts.
Key Insight
For each cell, if the cell is 1, the longest line ending at that cell can be computed from a neighboring cell.
For a cell (i, j):
| Direction | Previous cell |
|---|---|
| Horizontal | (i, j - 1) |
| Vertical | (i - 1, j) |
| Diagonal | (i - 1, j - 1) |
| Anti-diagonal | (i - 1, j + 1) |
If mat[i][j] == 1, then:
horizontal = previous_horizontal + 1
vertical = previous_vertical + 1
diagonal = previous_diagonal + 1
anti_diagonal = previous_anti_diagonal + 1If mat[i][j] == 0, all four counts ending at that cell are 0.
This is dynamic programming.
Dynamic Programming State
Let:
dp[i][j][0]be the horizontal line length of consecutive 1 values ending at (i, j).
Similarly:
| State | Meaning |
|---|---|
dp[i][j][0] | Horizontal length ending at (i, j) |
dp[i][j][1] | Vertical length ending at (i, j) |
dp[i][j][2] | Diagonal length ending at (i, j) |
dp[i][j][3] | Anti-diagonal length ending at (i, j) |
For every 1 cell, update these four values and update the answer.
Algorithm
- Let
m = len(mat)andn = len(mat[0]). - Create a DP table of shape
m x n x 4. - Initialize
ans = 0. - For each cell
(i, j):- If
mat[i][j] == 0, skip it. - Set the horizontal count from the left neighbor.
- Set the vertical count from the upper neighbor.
- Set the diagonal count from the upper-left neighbor.
- Set the anti-diagonal count from the upper-right neighbor.
- Update
ans.
- If
Correctness
For any cell containing 0, no line of consecutive 1 values can end at that cell. The algorithm correctly leaves all four counts as 0.
For any cell (i, j) containing 1, a horizontal line ending at (i, j) either has length 1, or it extends a horizontal line ending at (i, j - 1). Therefore, the horizontal recurrence is correct.
The same reasoning applies to the other directions. A vertical line extends from (i - 1, j), a diagonal line extends from (i - 1, j - 1), and an anti-diagonal line extends from (i - 1, j + 1).
Every valid line of consecutive 1 values has a final cell in its direction. When the algorithm processes that final cell, the DP value for that direction equals the length of the line. Since the algorithm takes the maximum over all cells and all four directions, it returns the length of the longest valid line.
Complexity
Let m be the number of rows and n be the number of columns.
| Metric | Value | Why |
|---|---|---|
| Time | O(mn) | Each cell is processed once |
| Space | O(mn) | The DP table stores four values per cell |
Implementation
class Solution:
def longestLine(self, mat: list[list[int]]) -> int:
if not mat or not mat[0]:
return 0
m = len(mat)
n = len(mat[0])
dp = [[[0] * 4 for _ in range(n)] for _ in range(m)]
ans = 0
for i in range(m):
for j in range(n):
if mat[i][j] == 0:
continue
dp[i][j][0] = dp[i][j - 1][0] + 1 if j > 0 else 1
dp[i][j][1] = dp[i - 1][j][1] + 1 if i > 0 else 1
dp[i][j][2] = dp[i - 1][j - 1][2] + 1 if i > 0 and j > 0 else 1
dp[i][j][3] = dp[i - 1][j + 1][3] + 1 if i > 0 and j + 1 < n else 1
ans = max(ans, max(dp[i][j]))
return ansCode Explanation
The DP table stores four counts for every cell:
dp = [[[0] * 4 for _ in range(n)] for _ in range(m)]When the current cell is 0, we skip it:
if mat[i][j] == 0:
continueA horizontal line ending at (i, j) extends from the left:
dp[i][j][0] = dp[i][j - 1][0] + 1 if j > 0 else 1A vertical line extends from above:
dp[i][j][1] = dp[i - 1][j][1] + 1 if i > 0 else 1A diagonal line extends from the upper-left cell:
dp[i][j][2] = dp[i - 1][j - 1][2] + 1 if i > 0 and j > 0 else 1An anti-diagonal line extends from the upper-right cell:
dp[i][j][3] = dp[i - 1][j + 1][3] + 1 if i > 0 and j + 1 < n else 1After computing the four values, we update the answer:
ans = max(ans, max(dp[i][j]))Space-Optimized Version
We can reduce memory by keeping only the previous row and the current row.
This works because each DP value only depends on:
- The current row’s left cell.
- The previous row.
- The previous row’s left or right neighbor.
class Solution:
def longestLine(self, mat: list[list[int]]) -> int:
if not mat or not mat[0]:
return 0
m = len(mat)
n = len(mat[0])
prev = [[0] * 4 for _ in range(n)]
ans = 0
for i in range(m):
curr = [[0] * 4 for _ in range(n)]
for j in range(n):
if mat[i][j] == 0:
continue
curr[j][0] = curr[j - 1][0] + 1 if j > 0 else 1
curr[j][1] = prev[j][1] + 1
curr[j][2] = prev[j - 1][2] + 1 if j > 0 else 1
curr[j][3] = prev[j + 1][3] + 1 if j + 1 < n else 1
ans = max(ans, max(curr[j]))
prev = curr
return ansThis version uses O(n) space.
Testing
def run_tests():
s = Solution()
assert s.longestLine([
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 1],
]) == 3
assert s.longestLine([[0]]) == 0
assert s.longestLine([[1]]) == 1
assert s.longestLine([
[1, 1, 1, 1],
]) == 4
assert s.longestLine([
[1],
[1],
[1],
]) == 3
assert s.longestLine([
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
]) == 3
assert s.longestLine([
[0, 0, 1],
[0, 1, 0],
[1, 0, 0],
]) == 3
print("all tests passed")
run_tests()| Test | Why |
|---|---|
| Main sample | Checks diagonal and normal mixed matrix |
[[0]] | Single zero |
[[1]] | Single one |
| One row of ones | Horizontal line |
| One column of ones | Vertical line |
| Main diagonal | Diagonal direction |
| Anti-diagonal | Anti-diagonal direction |