|
| 1 | +# [Problem 2435: Paths in Matrix Whose Sum Is Divisible by K](https://leetcode.com/problems/paths-in-matrix-whose-sum-is-divisible-by-k/description/?envType=daily-question) |
| 2 | + |
| 3 | +## Initial thoughts (stream-of-consciousness) |
| 4 | +We need to count number of down/right paths from (0,0) to (m-1,n-1) such that the sum of visited grid values is divisible by k. This is a classic DP over grid where each DP state must remember sum modulo k. Natural DP: dp[i][j][r] = number of ways to reach (i,j) with current sum % k == r. Transition from top or left: shift remainder by grid[i][j] and add counts. |
| 5 | + |
| 6 | +However naive 3D DP storing states for all cells may be memory-heavy in Python because k ≤ 50 and m*n ≤ 5e4 — worst-case could be many integers. We can reduce memory by only keeping previous row and current row (since transitions only from top and left). Also we can transpose the grid so the number of columns is the smaller dimension (to minimize memory for per-column arrays). Complexity will be O(m * n * k) time and O(n * k) memory (after transpose ensuring n ≤ m). |
| 7 | + |
| 8 | +Edge cases: single row or single column (only one path), k = 1 (everything divisible), but DP handles these naturally. Transposing when n > m keeps memory small. |
| 9 | + |
| 10 | +## Refining the problem, round 2 thoughts |
| 11 | +- Ensure we transpose the grid so the number of columns (n) ≤ number of rows (m) — that makes O(n*k) memory safe because n ≤ sqrt(m*n) ≤ sqrt(5e4) ≈ 224. |
| 12 | +- Use two arrays (prev row and cur row) where each entry is a length-k list of counts. |
| 13 | +- Initialize dp at (0,0) with remainder grid[0][0] % k = 1 way. |
| 14 | +- For cell (i,j) add contributions from prev[j] (top) and cur[j-1] (left), shifting indices by cell value modulo k. |
| 15 | +- Use modulo 10^9+7 for counts. |
| 16 | +- Time complexity O(m*n*k) with m*n ≤ 5e4 and k ≤ 50 so ≲ 2.5e6 remainder-updates; memory O(n*k). |
| 17 | + |
| 18 | +Now provide the full Python solution. |
| 19 | + |
| 20 | +## Attempted solution(s) |
| 21 | +```python |
| 22 | +from typing import List |
| 23 | + |
| 24 | +class Solution: |
| 25 | + def numberOfPaths(self, grid: List[List[int]], k: int) -> int: |
| 26 | + MOD = 10**9 + 7 |
| 27 | + m = len(grid) |
| 28 | + n = len(grid[0]) |
| 29 | + # Transpose if columns > rows to minimize number of columns (and memory) |
| 30 | + if n > m: |
| 31 | + grid = [list(row) for row in zip(*grid)] |
| 32 | + m, n = n, m # swapped |
| 33 | + |
| 34 | + # prev[j] is a list of length k: counts for column j in previous row |
| 35 | + prev = [ [0]*k for _ in range(n) ] |
| 36 | + |
| 37 | + for i in range(m): |
| 38 | + cur = [ [0]*k for _ in range(n) ] |
| 39 | + for j in range(n): |
| 40 | + val = grid[i][j] % k |
| 41 | + if i == 0 and j == 0: |
| 42 | + cur[0][val] = 1 |
| 43 | + else: |
| 44 | + # from top (prev row same column) |
| 45 | + if i > 0: |
| 46 | + top = prev[j] |
| 47 | + # shift each remainder by val |
| 48 | + if val == 0: |
| 49 | + # small optimization: if val 0, remainders don't shift |
| 50 | + for r in range(k): |
| 51 | + cnt = top[r] |
| 52 | + if cnt: |
| 53 | + cur[j][r] = (cur[j][r] + cnt) % MOD |
| 54 | + else: |
| 55 | + for r in range(k): |
| 56 | + cnt = top[r] |
| 57 | + if cnt: |
| 58 | + newr = (r + val) % k |
| 59 | + cur[j][newr] = (cur[j][newr] + cnt) % MOD |
| 60 | + |
| 61 | + # from left (current row previous column) |
| 62 | + if j > 0: |
| 63 | + left = cur[j-1] |
| 64 | + if val == 0: |
| 65 | + for r in range(k): |
| 66 | + cnt = left[r] |
| 67 | + if cnt: |
| 68 | + cur[j][r] = (cur[j][r] + cnt) % MOD |
| 69 | + else: |
| 70 | + for r in range(k): |
| 71 | + cnt = left[r] |
| 72 | + if cnt: |
| 73 | + newr = (r + val) % k |
| 74 | + cur[j][newr] = (cur[j][newr] + cnt) % MOD |
| 75 | + prev = cur |
| 76 | + |
| 77 | + # answer is number of ways to reach last cell with remainder 0 |
| 78 | + return prev[n-1][0] % MOD |
| 79 | +``` |
| 80 | +- Notes on approach: |
| 81 | + - We transpose grid when needed to ensure the number of columns n ≤ rows m so memory O(n*k) is small. |
| 82 | + - prev and cur store counts per remainder for each column. For each cell we combine contributions from top (prev[j]) and left (cur[j-1]) after shifting remainders by grid[i][j] % k. |
| 83 | + - Time complexity: O(m * n * k). Given constraints m * n ≤ 5e4 and k ≤ 50, this is at most about 2.5e6 remainder-updates — efficient. |
| 84 | + - Space complexity: O(n * k) after transpose (n ≤ sqrt(m*n)), which fits comfortably in memory. |
0 commit comments