2245. Maximum Trailing Zeros in a Cornered Path
Problem Description
You are given a 2D integer array grid
of size m x n
, where each cell contains a positive integer.
A cornered path is a path through the grid that follows these rules:
- The path consists of adjacent cells
- The path can have at most one turn (it can also have no turns)
- Before the turn (if there is one), the path moves exclusively in one direction - either horizontally (left/right) or vertically (up/down)
- After the turn, the path must move exclusively in the alternate direction - if it was moving horizontally before, it must move vertically after, and vice versa
- The path cannot revisit any cell
The product of a path is calculated by multiplying all the values in the cells along that path.
The trailing zeros of a number are the consecutive zeros at the end of the number. For example, 1200
has 2 trailing zeros, and 5040
has 1 trailing zero.
Your task is to find the maximum number of trailing zeros that can be obtained from the product of any valid cornered path in the grid.
Key Points:
- Horizontal movement means moving left or right
- Vertical movement means moving up or down
- A cornered path can be a straight line (0 turns) or have exactly 1 turn
- You want to maximize the trailing zeros in the product, not the product itself
For example, if a path covers cells with values [2, 5, 10]
, the product would be 2 × 5 × 10 = 100
, which has 2 trailing zeros.
Intuition
To understand how to maximize trailing zeros, we need to recognize that trailing zeros come from factors of 10, and 10 = 2 × 5
. Therefore, the number of trailing zeros in a product equals min(count of 2s, count of 5s)
in the prime factorization.
For example, if we have factors containing three 2s and two 5s, we can form two 10s, giving us 2 trailing zeros.
Now, let's think about the cornered path structure. A cornered path has at most one turn, which means it can only have these shapes:
- Straight line (no turn): horizontal or vertical
- L-shaped path (one turn): starts horizontally then turns vertically, or starts vertically then turns horizontally
The key insight is that any cornered path that maximizes the product should start from a boundary and end at another boundary. Why? Because we want to include as many cells as possible to accumulate more factors of 2 and 5.
This leads us to realize that for any cell (i, j)
that serves as a turning point, there are exactly 4 possible L-shaped paths:
- Start from the left boundary, move right to
(i, j)
, then turn up to the top boundary - Start from the left boundary, move right to
(i, j)
, then turn down to the bottom boundary - Start from the right boundary, move left to
(i, j)
, then turn up to the top boundary - Start from the right boundary, move left to
(i, j)
, then turn down to the bottom boundary
To efficiently calculate the factors of 2 and 5 for any path segment, we can use prefix sums. We precompute:
- Row-wise prefix sums for counts of 2 and 5 (to handle horizontal segments)
- Column-wise prefix sums for counts of 2 and 5 (to handle vertical segments)
With these prefix sums, for any turning point (i, j)
, we can quickly calculate the total count of 2s and 5s for each of the 4 possible L-shaped paths, take the minimum of the two counts to get trailing zeros, and track the maximum across all possibilities.
Learn more about Prefix Sum patterns.
Solution Approach
Based on our intuition, let's implement the solution step by step:
Step 1: Initialize Prefix Sum Arrays
We create four 2D arrays with dimensions (m+1) × (n+1)
to store prefix sums:
r2[i][j]
: count of factor 2 from column 1 to column j in row ir5[i][j]
: count of factor 5 from column 1 to column j in row ic2[i][j]
: count of factor 2 from row 1 to row i in column jc5[i][j]
: count of factor 5 from row 1 to row i in column j
We use 1-indexed arrays (with size m+1
and n+1
) to simplify calculations and avoid boundary checks.
Step 2: Build Prefix Sums
For each cell grid[i-1][j-1]
(using 1-based indexing in our arrays):
-
Count the factors of 2 and 5 in the current value:
while x % 2 == 0: x //= 2 s2 += 1 while x % 5 == 0: x //= 5 s5 += 1
-
Update the prefix sums:
- Row prefix:
r2[i][j] = r2[i][j-1] + s2
andr5[i][j] = r5[i][j-1] + s5
- Column prefix:
c2[i][j] = c2[i-1][j] + s2
andc5[i][j] = c5[i-1][j] + s5
- Row prefix:
Step 3: Enumerate Turning Points
For each cell (i, j)
as a potential turning point, calculate the trailing zeros for all 4 possible L-shaped paths:
-
Path a: Left boundary →
(i,j)
→ Top boundary- Horizontal segment:
r2[i][j]
andr5[i][j]
(from column 1 to j in row i) - Vertical segment:
c2[i-1][j]
andc5[i-1][j]
(from row 1 to i-1 in column j) - Total:
min(r2[i][j] + c2[i-1][j], r5[i][j] + c5[i-1][j])
- Horizontal segment:
-
Path b: Left boundary →
(i,j)
→ Bottom boundary- Horizontal segment:
r2[i][j]
andr5[i][j]
- Vertical segment:
c2[m][j] - c2[i][j]
andc5[m][j] - c5[i][j]
(from row i+1 to m) - Total:
min(r2[i][j] + c2[m][j] - c2[i][j], r5[i][j] + c5[m][j] - c5[i][j])
- Horizontal segment:
-
Path c: Right boundary →
(i,j)
→ Top boundary- Horizontal segment:
r2[i][n] - r2[i][j]
andr5[i][n] - r5[i][j]
(from column j+1 to n) - Vertical segment:
c2[i][j]
andc5[i][j]
(from row 1 to i) - Total:
min(r2[i][n] - r2[i][j] + c2[i][j], r5[i][n] - r5[i][j] + c5[i][j])
- Horizontal segment:
-
Path d: Right boundary →
(i,j)
→ Bottom boundary- Horizontal segment:
r2[i][n] - r2[i][j-1]
andr5[i][n] - r5[i][j-1]
(from column j to n, inclusive) - Vertical segment:
c2[m][j] - c2[i][j]
andc5[m][j] - c5[i][j]
- Total:
min(r2[i][n] - r2[i][j-1] + c2[m][j] - c2[i][j], r5[i][n] - r5[i][j-1] + c5[m][j] - c5[i][j])
- Horizontal segment:
Note that for path d, we use r2[i][j-1]
instead of r2[i][j]
to include cell (i,j)
in the horizontal segment.
Step 4: Track Maximum
For each turning point, we take the maximum of all four path values and update our answer. The final answer is the maximum trailing zeros across all possible cornered paths in the grid.
The time complexity is O(m × n)
for building prefix sums and enumerating turning points, with O(m × n)
space for the prefix sum arrays.
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 to illustrate the solution approach.
Consider a 3×3 grid:
grid = [[4, 5, 2], [10, 3, 6], [2, 25, 8]]
Step 1: Count factors of 2 and 5 for each cell
First, let's break down each cell into its factors of 2 and 5:
4 = 2²
→ 2 twos, 0 fives5 = 5¹
→ 0 twos, 1 five2 = 2¹
→ 1 two, 0 fives10 = 2¹×5¹
→ 1 two, 1 five3 = 3¹
→ 0 twos, 0 fives6 = 2¹×3
→ 1 two, 0 fives2 = 2¹
→ 1 two, 0 fives25 = 5²
→ 0 twos, 2 fives8 = 2³
→ 3 twos, 0 fives
Step 2: Build prefix sum arrays
Row prefix sums for factors of 2 (r2):
r2 = [[0, 0, 0, 0], [0, 2, 2, 3], // row 1: accumulate 2, 0, 1 [0, 1, 1, 2], // row 2: accumulate 1, 0, 1 [0, 1, 1, 4]] // row 3: accumulate 1, 0, 3
Row prefix sums for factors of 5 (r5):
r5 = [[0, 0, 0, 0], [0, 0, 1, 1], // row 1: accumulate 0, 1, 0 [0, 1, 1, 1], // row 2: accumulate 1, 0, 0 [0, 0, 2, 2]] // row 3: accumulate 0, 2, 0
Column prefix sums for factors of 2 (c2):
c2 = [[0, 0, 0, 0], [0, 2, 0, 1], // col accumulations [0, 3, 0, 2], [0, 4, 0, 5]]
Column prefix sums for factors of 5 (c5):
c5 = [[0, 0, 0, 0], [0, 0, 1, 0], // col accumulations [0, 1, 1, 0], [0, 1, 3, 0]]
Step 3: Calculate L-shaped paths for turning point (2,2)
Let's examine cell (2,2) with value 3 as a turning point. Using 1-based indexing in our prefix arrays, this is position (2,2).
Path a: Left boundary → (2,2) → Top boundary
- Horizontal: cells [10, 3] give r2[2][2] = 1 and r5[2][2] = 1
- Vertical: cell [5] gives c2[1][2] = 0 and c5[1][2] = 1
- Total factors: 1+0 = 1 two, 1+1 = 2 fives
- Trailing zeros: min(1, 2) = 1
Path b: Left boundary → (2,2) → Bottom boundary
- Horizontal: cells [10, 3] give r2[2][2] = 1 and r5[2][2] = 1
- Vertical: cell [25] gives c2[3][2] - c2[2][2] = 0-0 = 0 and c5[3][2] - c5[2][2] = 3-1 = 2
- Total factors: 1+0 = 1 two, 1+2 = 3 fives
- Trailing zeros: min(1, 3) = 1
Path c: Right boundary → (2,2) → Top boundary
- Horizontal: cell [6] gives r2[2][3] - r2[2][2] = 2-1 = 1 and r5[2][3] - r5[2][2] = 1-1 = 0
- Vertical: cells [5, 3] give c2[2][2] = 0 and c5[2][2] = 1
- Total factors: 1+0 = 1 two, 0+1 = 1 five
- Trailing zeros: min(1, 1) = 1
Path d: Right boundary → (2,2) → Bottom boundary
- Horizontal: cells [3, 6] give r2[2][3] - r2[2][1] = 2-1 = 1 and r5[2][3] - r5[2][1] = 1-1 = 0
- Vertical: cell [25] gives c2[3][2] - c2[2][2] = 0-0 = 0 and c5[3][2] - c5[2][2] = 3-1 = 2
- Total factors: 1+0 = 1 two, 0+2 = 2 fives
- Trailing zeros: min(1, 2) = 1
Step 4: Check other turning points
Let's check one more promising turning point at (1,2) with value 5:
Path b: Left boundary → (1,2) → Bottom boundary
- Horizontal: cells [4, 5] give r2[1][2] = 2 and r5[1][2] = 1
- Vertical: cells [3, 25] give c2[3][2] - c2[1][2] = 0-0 = 0 and c5[3][2] - c5[1][2] = 3-1 = 2
- Total factors: 2+0 = 2 twos, 1+2 = 3 fives
- Trailing zeros: min(2, 3) = 2
This gives us 2 trailing zeros! After checking all possible turning points and their L-shaped paths, the maximum trailing zeros would be 2.
Solution Implementation
1class Solution:
2 def maxTrailingZeros(self, grid: List[List[int]]) -> int:
3 rows, cols = len(grid), len(grid[0])
4
5 # Prefix sum arrays for counting factors of 2 and 5
6 # row_twos[i][j] = count of factor 2 in row i from column 0 to j-1
7 # col_twos[i][j] = count of factor 2 in column j from row 0 to i-1
8 row_twos = [[0] * (cols + 1) for _ in range(rows + 1)]
9 col_twos = [[0] * (cols + 1) for _ in range(rows + 1)]
10 row_fives = [[0] * (cols + 1) for _ in range(rows + 1)]
11 col_fives = [[0] * (cols + 1) for _ in range(rows + 1)]
12
13 # Build prefix sum arrays by counting factors in each cell
14 for i, row in enumerate(grid, 1):
15 for j, value in enumerate(row, 1):
16 # Count factors of 2 in current cell
17 count_twos = 0
18 while value % 2 == 0:
19 value //= 2
20 count_twos += 1
21
22 # Count factors of 5 in current cell
23 count_fives = 0
24 while value % 5 == 0:
25 value //= 5
26 count_fives += 1
27
28 # Update prefix sums for rows and columns
29 row_twos[i][j] = row_twos[i][j - 1] + count_twos
30 col_twos[i][j] = col_twos[i - 1][j] + count_twos
31 row_fives[i][j] = row_fives[i][j - 1] + count_fives
32 col_fives[i][j] = col_fives[i - 1][j] + count_fives
33
34 max_trailing_zeros = 0
35
36 # Check all possible L-shaped paths with turning point at (i, j)
37 for i in range(1, rows + 1):
38 for j in range(1, cols + 1):
39 # Path 1: Left to (i,j), then up
40 path1_zeros = min(
41 row_twos[i][j] + col_twos[i - 1][j],
42 row_fives[i][j] + col_fives[i - 1][j]
43 )
44
45 # Path 2: Left to (i,j), then down
46 path2_zeros = min(
47 row_twos[i][j] + col_twos[rows][j] - col_twos[i][j],
48 row_fives[i][j] + col_fives[rows][j] - col_fives[i][j]
49 )
50
51 # Path 3: Right from (i,j), then up including (i,j)
52 path3_zeros = min(
53 row_twos[i][cols] - row_twos[i][j] + col_twos[i][j],
54 row_fives[i][cols] - row_fives[i][j] + col_fives[i][j]
55 )
56
57 # Path 4: Right from (i,j), then down
58 path4_zeros = min(
59 row_twos[i][cols] - row_twos[i][j - 1] + col_twos[rows][j] - col_twos[i][j],
60 row_fives[i][cols] - row_fives[i][j - 1] + col_fives[rows][j] - col_fives[i][j]
61 )
62
63 # Update maximum trailing zeros
64 max_trailing_zeros = max(
65 max_trailing_zeros,
66 path1_zeros,
67 path2_zeros,
68 path3_zeros,
69 path4_zeros
70 )
71
72 return max_trailing_zeros
73
1class Solution {
2 public int maxTrailingZeros(int[][] grid) {
3 int rows = grid.length;
4 int cols = grid[0].length;
5
6 // Prefix sum arrays for counting factors of 2 and 5
7 // rowFactorsOf2[i][j] = count of factor 2 from grid[i-1][0] to grid[i-1][j-1] in row i
8 int[][] rowFactorsOf2 = new int[rows + 1][cols + 1];
9 // colFactorsOf2[i][j] = count of factor 2 from grid[0][j-1] to grid[i-1][j-1] in column j
10 int[][] colFactorsOf2 = new int[rows + 1][cols + 1];
11 // Similarly for factor 5
12 int[][] rowFactorsOf5 = new int[rows + 1][cols + 1];
13 int[][] colFactorsOf5 = new int[rows + 1][cols + 1];
14
15 // Build prefix sum arrays by counting factors of 2 and 5 for each cell
16 for (int i = 1; i <= rows; i++) {
17 for (int j = 1; j <= cols; j++) {
18 int currentValue = grid[i - 1][j - 1];
19
20 // Count factors of 2 in current cell
21 int factorOf2Count = 0;
22 while (currentValue % 2 == 0) {
23 factorOf2Count++;
24 currentValue /= 2;
25 }
26
27 // Count factors of 5 in current cell (continue with modified value)
28 int factorOf5Count = 0;
29 while (currentValue % 5 == 0) {
30 factorOf5Count++;
31 currentValue /= 5;
32 }
33
34 // Update prefix sums for current row and column
35 rowFactorsOf2[i][j] = rowFactorsOf2[i][j - 1] + factorOf2Count;
36 colFactorsOf2[i][j] = colFactorsOf2[i - 1][j] + factorOf2Count;
37 rowFactorsOf5[i][j] = rowFactorsOf5[i][j - 1] + factorOf5Count;
38 colFactorsOf5[i][j] = colFactorsOf5[i - 1][j] + factorOf5Count;
39 }
40 }
41
42 int maxTrailingZeros = 0;
43
44 // Check all possible L-shaped paths with corner at each cell (i, j)
45 for (int i = 1; i <= rows; i++) {
46 for (int j = 1; j <= cols; j++) {
47 // Path 1: Left + Up (from (i,j) go left to column 1, then up to row 1)
48 int leftUp = Math.min(
49 rowFactorsOf2[i][j] + colFactorsOf2[i - 1][j],
50 rowFactorsOf5[i][j] + colFactorsOf5[i - 1][j]
51 );
52
53 // Path 2: Left + Down (from (i,j) go left to column 1, then down to last row)
54 int leftDown = Math.min(
55 rowFactorsOf2[i][j] + colFactorsOf2[rows][j] - colFactorsOf2[i][j],
56 rowFactorsOf5[i][j] + colFactorsOf5[rows][j] - colFactorsOf5[i][j]
57 );
58
59 // Path 3: Right + Up (from (i,j) go right to last column, then up to row 1)
60 int rightUp = Math.min(
61 rowFactorsOf2[i][cols] - rowFactorsOf2[i][j] + colFactorsOf2[i][j],
62 rowFactorsOf5[i][cols] - rowFactorsOf5[i][j] + colFactorsOf5[i][j]
63 );
64
65 // Path 4: Right + Down (from (i,j) go right to last column, then down to last row)
66 int rightDown = Math.min(
67 rowFactorsOf2[i][cols] - rowFactorsOf2[i][j - 1] + colFactorsOf2[rows][j] - colFactorsOf2[i][j],
68 rowFactorsOf5[i][cols] - rowFactorsOf5[i][j - 1] + colFactorsOf5[rows][j] - colFactorsOf5[i][j]
69 );
70
71 // Update maximum trailing zeros found
72 maxTrailingZeros = Math.max(maxTrailingZeros,
73 Math.max(leftUp, Math.max(leftDown, Math.max(rightUp, rightDown))));
74 }
75 }
76
77 return maxTrailingZeros;
78 }
79}
80
1class Solution {
2public:
3 int maxTrailingZeros(vector<vector<int>>& grid) {
4 int rows = grid.size(), cols = grid[0].size();
5
6 // Prefix sum arrays for counting factors of 2 and 5
7 // row2[i][j] = count of factor 2 in row i from column 1 to j
8 // col2[i][j] = count of factor 2 in column j from row 1 to i
9 // row5[i][j] = count of factor 5 in row i from column 1 to j
10 // col5[i][j] = count of factor 5 in column j from row 1 to i
11 vector<vector<int>> row2(rows + 1, vector<int>(cols + 1));
12 vector<vector<int>> col2(rows + 1, vector<int>(cols + 1));
13 vector<vector<int>> row5(rows + 1, vector<int>(cols + 1));
14 vector<vector<int>> col5(rows + 1, vector<int>(cols + 1));
15
16 // Build prefix sum arrays
17 for (int i = 1; i <= rows; ++i) {
18 for (int j = 1; j <= cols; ++j) {
19 int value = grid[i - 1][j - 1];
20 int count2 = 0, count5 = 0;
21
22 // Count factors of 2 in current cell
23 while (value % 2 == 0) {
24 value /= 2;
25 ++count2;
26 }
27
28 // Count factors of 5 in current cell
29 while (value % 5 == 0) {
30 value /= 5;
31 ++count5;
32 }
33
34 // Update prefix sums
35 row2[i][j] = row2[i][j - 1] + count2; // Accumulate along row
36 col2[i][j] = col2[i - 1][j] + count2; // Accumulate along column
37 row5[i][j] = row5[i][j - 1] + count5; // Accumulate along row
38 col5[i][j] = col5[i - 1][j] + count5; // Accumulate along column
39 }
40 }
41
42 int maxZeros = 0;
43
44 // Check all possible L-shaped paths with each cell as the corner
45 for (int i = 1; i <= rows; ++i) {
46 for (int j = 1; j <= cols; ++j) {
47 // Four possible L-shaped paths:
48 // 1. Left + Up: horizontal from (i,1) to (i,j) + vertical from (1,j) to (i-1,j)
49 int leftUp = min(row2[i][j] + col2[i - 1][j],
50 row5[i][j] + col5[i - 1][j]);
51
52 // 2. Left + Down: horizontal from (i,1) to (i,j) + vertical from (i,j) to (rows,j)
53 int leftDown = min(row2[i][j] + col2[rows][j] - col2[i][j],
54 row5[i][j] + col5[rows][j] - col5[i][j]);
55
56 // 3. Right + Up: horizontal from (i,j) to (i,cols) + vertical from (1,j) to (i,j)
57 int rightUp = min(row2[i][cols] - row2[i][j] + col2[i][j],
58 row5[i][cols] - row5[i][j] + col5[i][j]);
59
60 // 4. Right + Down: horizontal from (i,j+1) to (i,cols) + vertical from (i,j) to (rows,j)
61 int rightDown = min(row2[i][cols] - row2[i][j - 1] + col2[rows][j] - col2[i][j],
62 row5[i][cols] - row5[i][j - 1] + col5[rows][j] - col5[i][j]);
63
64 // Update maximum trailing zeros
65 maxZeros = max({maxZeros, leftUp, leftDown, rightUp, rightDown});
66 }
67 }
68
69 return maxZeros;
70 }
71};
72
1/**
2 * Finds the maximum number of trailing zeros in the product of numbers along any valid path.
3 * A valid path starts from any cell and moves only right or down, forming an L-shape.
4 * Trailing zeros are determined by min(count of factor 2, count of factor 5) in the product.
5 *
6 * @param grid - 2D array of positive integers
7 * @returns Maximum number of trailing zeros possible
8 */
9function maxTrailingZeros(grid: number[][]): number {
10 const rows = grid.length;
11 const cols = grid[0].length;
12
13 // Prefix sum arrays for counting factors of 2 and 5
14 // rowPrefix2[i][j] = sum of factor 2 counts from grid[i-1][0] to grid[i-1][j-1]
15 // colPrefix2[i][j] = sum of factor 2 counts from grid[0][j-1] to grid[i-1][j-1]
16 const rowPrefix2 = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
17 const colPrefix2 = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
18 const rowPrefix5 = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
19 const colPrefix5 = Array.from({ length: rows + 1 }, () => new Array(cols + 1).fill(0));
20
21 // Build prefix sum arrays by counting factors of 2 and 5 for each cell
22 for (let i = 1; i <= rows; ++i) {
23 for (let j = 1; j <= cols; ++j) {
24 let currentValue = grid[i - 1][j - 1];
25 let countOf2 = 0;
26 let countOf5 = 0;
27
28 // Count factors of 2 in current cell value
29 while (currentValue % 2 === 0) {
30 ++countOf2;
31 currentValue = Math.floor(currentValue / 2);
32 }
33
34 // Count factors of 5 in current cell value
35 while (currentValue % 5 === 0) {
36 ++countOf5;
37 currentValue = Math.floor(currentValue / 5);
38 }
39
40 // Update prefix sums for current row and column
41 rowPrefix2[i][j] = rowPrefix2[i][j - 1] + countOf2;
42 colPrefix2[i][j] = colPrefix2[i - 1][j] + countOf2;
43 rowPrefix5[i][j] = rowPrefix5[i][j - 1] + countOf5;
44 colPrefix5[i][j] = colPrefix5[i - 1][j] + countOf5;
45 }
46 }
47
48 let maxZeros = 0;
49
50 // Try each cell as the turning point of the L-shaped path
51 for (let i = 1; i <= rows; ++i) {
52 for (let j = 1; j <= cols; ++j) {
53 // Path type 1: Left to current cell, then up to top
54 const leftUp = Math.min(
55 rowPrefix2[i][j] + colPrefix2[i - 1][j],
56 rowPrefix5[i][j] + colPrefix5[i - 1][j]
57 );
58
59 // Path type 2: Left to current cell, then down to bottom
60 const leftDown = Math.min(
61 rowPrefix2[i][j] + colPrefix2[rows][j] - colPrefix2[i][j],
62 rowPrefix5[i][j] + colPrefix5[rows][j] - colPrefix5[i][j]
63 );
64
65 // Path type 3: Right from current cell, then up (including current cell)
66 const rightUp = Math.min(
67 rowPrefix2[i][cols] - rowPrefix2[i][j] + colPrefix2[i][j],
68 rowPrefix5[i][cols] - rowPrefix5[i][j] + colPrefix5[i][j]
69 );
70
71 // Path type 4: Right from current cell, then down (excluding double count)
72 const rightDown = Math.min(
73 rowPrefix2[i][cols] - rowPrefix2[i][j - 1] + colPrefix2[rows][j] - colPrefix2[i][j],
74 rowPrefix5[i][cols] - rowPrefix5[i][j - 1] + colPrefix5[rows][j] - colPrefix5[i][j]
75 );
76
77 // Update maximum trailing zeros
78 maxZeros = Math.max(maxZeros, leftUp, leftDown, rightUp, rightDown);
79 }
80 }
81
82 return maxZeros;
83}
84
Time and Space Complexity
Time Complexity: O(m × n)
The algorithm consists of two main phases:
-
Preprocessing phase: The first nested loop iterates through all
m × n
cells of the grid. For each cell, it counts the factors of 2 and 5, which takesO(log(max_value))
time per cell. Since the maximum value in the grid is bounded, this is effectivelyO(1)
per cell. The loop also updates the prefix sum arraysr2
,c2
,r5
, andc5
inO(1)
time per cell. Therefore, this phase takesO(m × n)
time. -
Computation phase: The second nested loop again iterates through all
m × n
cells. For each cell(i, j)
, it computes four possible L-shaped paths using the precomputed prefix sums. Each computation involves constant-time arithmetic operations using the prefix sum arrays. Therefore, this phase also takesO(m × n)
time.
The total time complexity is O(m × n) + O(m × n) = O(m × n)
.
Space Complexity: O(m × n)
The algorithm uses four 2D arrays (r2
, c2
, r5
, c5
), each of size (m + 1) × (n + 1)
. Each array stores prefix sums for either factor 2 or factor 5, either row-wise or column-wise. The space required is 4 × (m + 1) × (n + 1) = O(m × n)
.
The additional variables (ans
, a
, b
, c
, d
, s2
, s5
) use O(1)
space.
Therefore, the total space complexity is O(m × n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Handling of Path Inclusion at Turning Point
One of the most common mistakes is incorrectly including or excluding the turning point cell when calculating the path segments. This leads to either double-counting the turning point or missing it entirely.
The Problem:
When calculating L-shaped paths, the turning point cell (i,j)
should be counted exactly once. However, it's easy to make mistakes like:
- Double-counting: Including it in both horizontal and vertical segments
- Missing it: Excluding it from both segments
- Inconsistent handling: Different inclusion logic for different path types
Example of the Bug:
# Incorrect: Path 3 - Right from (i,j) then up
# This excludes the turning point from both segments!
path3_zeros = min(
row_twos[i][cols] - row_twos[i][j], # Excludes column j
col_twos[i-1][j] # Excludes row i
)
The Solution: The turning point should be included in exactly one segment. The correct approach:
# Correct: Include turning point in vertical segment
path3_zeros = min(
row_twos[i][cols] - row_twos[i][j] + col_twos[i][j],
row_fives[i][cols] - row_fives[i][j] + col_fives[i][j]
)
# Here col_twos[i][j] includes the turning point
2. Confusion with Prefix Sum Boundaries
Another pitfall is misunderstanding what the prefix sum values represent, especially with 1-based indexing.
The Problem: With 1-based indexing:
row_twos[i][j]
represents the sum from column 1 to column j in row icol_twos[i][j]
represents the sum from row 1 to row i in column j
It's easy to forget whether boundaries are inclusive or exclusive.
Example of the Bug:
# Incorrect: Trying to get sum from column j+1 to n path3_horizontal = row_twos[i][cols] - row_twos[i][j+1] # Wrong!
The Solution:
Remember that to get the sum from column j+1
to n
, you subtract the prefix sum up to column j
:
# Correct: Sum from column j+1 to n path3_horizontal = row_twos[i][cols] - row_twos[i][j]
3. Not Considering Straight Line Paths
The problem states paths can have "at most one turn," meaning straight lines (0 turns) are also valid.
The Problem: The code only considers L-shaped paths with exactly one turn, missing potentially better straight-line paths.
The Solution: Add checks for straight horizontal and vertical paths:
# Add straight line paths
for i in range(1, rows + 1):
# Horizontal line across entire row
horizontal_zeros = min(row_twos[i][cols], row_fives[i][cols])
max_trailing_zeros = max(max_trailing_zeros, horizontal_zeros)
for j in range(1, cols + 1):
# Vertical line down entire column
vertical_zeros = min(col_twos[rows][j], col_fives[rows][j])
max_trailing_zeros = max(max_trailing_zeros, vertical_zeros)
4. Integer Overflow in Factor Counting
While less common in Python, continuously dividing by 2 or 5 without bounds checking could theoretically cause issues with very large numbers.
The Solution: Add a reasonable upper bound or use more efficient factor counting:
def count_factor(n, factor):
count = 0
while n > 0 and n % factor == 0:
n //= factor
count += 1
return count
What does the following code do?
1def f(arr1, arr2):
2 i, j = 0, 0
3 new_arr = []
4 while i < len(arr1) and j < len(arr2):
5 if arr1[i] < arr2[j]:
6 new_arr.append(arr1[i])
7 i += 1
8 else:
9 new_arr.append(arr2[j])
10 j += 1
11 new_arr.extend(arr1[i:])
12 new_arr.extend(arr2[j:])
13 return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2 int i = 0, j = 0;
3 List<Integer> newArr = new ArrayList<>();
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.add(arr1[i]);
8 i++;
9 } else {
10 newArr.add(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.add(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.add(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
1function f(arr1, arr2) {
2 let i = 0, j = 0;
3 let newArr = [];
4
5 while (i < arr1.length && j < arr2.length) {
6 if (arr1[i] < arr2[j]) {
7 newArr.push(arr1[i]);
8 i++;
9 } else {
10 newArr.push(arr2[j]);
11 j++;
12 }
13 }
14
15 while (i < arr1.length) {
16 newArr.push(arr1[i]);
17 i++;
18 }
19
20 while (j < arr2.length) {
21 newArr.push(arr2[j]);
22 j++;
23 }
24
25 return newArr;
26}
27
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
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
Want a Structured Path to Master System Design Too? Don’t Miss This!