008 — Grid Paths¶
Source¶
- Original source: CSES Problem Set — Dynamic Programming section
- Platform: CSES — https://cses.fi/problemset/task/1638
- Topic: dynamic programming
- Difficulty: Medium
- Pattern: Grid DP + obstacles
- Basic idea inherited from: #7 Unique Paths — কিন্তু কিছু cell-এ ফাঁদ (trap), সেগুলো দিয়ে যাওয়া যায় না
1. Problem in simple words¶
একটা n × n grid দেওয়া আছে। প্রতিটা cell হয় খালি (.) নয় ফাঁদ (*)। তুমি বাঁ-উপরের কোণ থেকে ডান-নিচের কোণে যেতে চাও, শুধু ডানে বা নিচে চলে। ফাঁদওয়ালা cell-এ পা রাখা যাবে না। কতগুলো আলাদা path আছে, গোনো — উত্তর 10^9 + 7 দিয়ে modulo।
2. Which basic idea does this inherit from?¶
ভিত্তি সরাসরি #7 Unique Paths। সেখানে প্রতিটা cell-এর path = উপরের + বাঁয়ের। এখানে একটাই পরিবর্তন: cell-টা যদি ফাঁদ হয়, তার path-সংখ্যা জোর করে 0 — সেখান দিয়ে কোনো path যেতে পারে না। বাকি সব structure অপরিবর্তিত।
3. What is the hidden math or logic?¶
লুকানো logic আগের মতোই: cell (r, c)-তে ঢোকার শেষ move উপর থেকে বা বাঁ থেকে, তাই path = দুই প্রতিবেশীর যোগ। কিন্তু একটা guard যোগ হয়: (r, c) যদি ফাঁদ হয়, তাহলে এই cell দিয়ে শূন্য path যায় (dp[r][c] = 0), যা downstream সব হিসাবেও সেই শূন্য ছড়িয়ে দেয়।
4. Which data structure is needed?¶
একটা 2D array dp (size n × n), dp[r][c] = start থেকে (r, c) পর্যন্ত valid path-এর সংখ্যা। সাথে আসল grid (কোথায় ফাঁদ জানতে)। চাইলে এক row রেখে O(n) space-এও করা যায়।
5. Why this data structure fits¶
State দুটো সংখ্যা: row আর column — তাই 2D array নিখুঁত, dp[r][c] cell-এর উত্তর ধরে। ফাঁদের তথ্যও 2D grid, তাই index মিলে যায়। Dependency শুধু উপরে আর বাঁয়ে, তাই row-by-row বাঁ-থেকে-ডান ভরলে নিরাপদ, আর এক row-তে compress করা যায়।
6. Real-life analogy¶
ভাবো সেই শহরের block-grid, কিন্তু কিছু চৌরাস্তা বন্ধ (রাস্তা খোঁড়া)। তুমি পূর্ব-দক্ষিণে হেঁটে গন্তব্যে যাচ্ছ। যেকোনো খোলা চৌরাস্তায় পথ = পশ্চিম + উত্তরের পথ; কিন্তু চৌরাস্তা বন্ধ থাকলে সেখানে পৌঁছানোর পথই 0 — আর সেই 0 পরের চৌরাস্তাগুলোর হিসাবেও বয়ে যায়।
7. Visual explanation¶
dp[r][c] = (r,c)-তে পৌঁছানোর path। * = ফাঁদ। 3×3 grid, মাঝে ফাঁদ:
grid : dp ভরা:
. . . c=0 c=1 c=2
. * . r=0 1 1 1
. . . r=1 1 0 1 <- (1,1) ফাঁদ, তাই 0
r=2 1 1 2
dp[0][*] = 1, dp[*][0] = 1 (প্রথম row/column, ফাঁদ না থাকলে)
dp[1][1] = ফাঁদ -> 0
dp[1][2] = dp[0][2] + dp[1][1] = 1 + 0 = 1
dp[2][1] = dp[1][1] + dp[2][0] = 0 + 1 = 1
dp[2][2] = dp[1][2] + dp[2][1] = 1 + 1 = 2 <- answer
ফাঁদের 0 দুদিকে ছড়িয়ে path কমিয়ে দিল: 6 থেকে 2।
8. Example input and output¶
Input grid (3x3): Output: 2
. . .
. * .
. . .
Edge case 1: start cell নিজে ফাঁদ ('*' top-left) -> Output: 0
Edge case 2: কোনো ফাঁদ নেই (3x3 সব '.') -> Output: 6 (= Unique Paths)
Edge case 3: end cell ফাঁদ -> Output: 0
9. Brute force thinking¶
প্রথম সরল চিন্তা: প্রতিটা cell থেকে recurse করে দুই দিকের path যোগ — কিন্তু ফাঁদ বা grid-এর বাইরে গেলে 0।
paths(r, c):
if r >= n or c >= n: return 0 # বাইরে
if grid[r][c] == '*': return 0 # ফাঁদ
if r == n-1 and c == n-1: return 1 # পৌঁছে গেছি
return paths(r+1, c) + paths(r, c+1)
answer = paths(0, 0)
10. Why brute force is slow¶
এই recursion exponential (প্রায় O(2^(2n))) — প্রতিটা cell দুই শাখা খোলে, আর মাঝের cell-গুলো বহু পথ দিয়ে বারবার গোনা হয় (overlapping subproblems)। CSES-এ n অনেক বড় (1000 পর্যন্ত), তাই এটা কার্যত কখনো শেষ হবে না।
11. Key observation¶
মূল observation: distinct cell মাত্র n × n-টা, আর ফাঁদ শুধু কিছু cell-কে 0 বানায়। প্রতিটা cell-এর path একবার গুনে রাখলে O(n^2)-এ কাজ শেষ; ফাঁদ handle করা মানে শুধু সেই cell-এ 0 বসিয়ে দেওয়া।
12. Optimized intuition¶
State (শব্দে): dp[r][c] = start (0,0) থেকে cell (r, c) পর্যন্ত পৌঁছানোর valid path-এর সংখ্যা (ফাঁদ এড়িয়ে)।
Transition:
if grid[r][c] == '*': dp[r][c] = 0 # ফাঁদ: কোনো path নেই
else: dp[r][c] = dp[r-1][c] + dp[r][c-1] # উপর + বাঁ
(grid-এর বাইরের প্রতিবেশীকে 0 ধরা হয়।)
Base case: dp[0][0] = 1 যদি start ফাঁদ না হয়, নয়তো 0।
Answer location: dp[n-1][n-1]। বড় উত্তরে প্রতি যোগে % (10**9 + 7)।
Memoization (top-down): উপরের paths(r,c) recursion-টাই রাখো, dict যোগ করো। Tabulation (bottom-up): row-by-row বাঁ-থেকে-ডান ভরো, ফাঁদ cell-এ 0। শুধু আগের row লাগে বলে এক row-তে compress করা যায়।
13. Thinking tweak¶
মোচড়: Unique Paths-কে নতুন করে শেখার দরকার নেই — শুধু একটা guard বসাও: "এই cell কি ফাঁদ? হলে 0।" এটাই DP-র একটা সাধারণ কৌশল: একটা পরিচিত recurrence-এর উপর একটা ছোট constraint চাপানো। কঙ্কাল এক, শুধু একটা if যোগ।
14. Step-by-step dry run¶
3×3 grid, মাঝে ফাঁদ, tabulation, modulo ছাড়া (ছোট মান):
- প্রথম row:
dp[0] = [1, 1, 1](কোনো ফাঁদ নেই) - প্রথম column:
dp[0][0]=dp[1][0]=dp[2][0]=1 - dp[1][1]: ফাঁদ → 0
- dp[1][2] = dp[0][2] + dp[1][1] = 1 + 0 = 1
- dp[2][1] = dp[1][1] + dp[2][0] = 0 + 1 = 1
- dp[2][2] = dp[1][2] + dp[2][1] = 1 + 1 = 2 → answer
15. Python solution¶
MOD = 10**9 + 7
# ---- way 1: memoization (top-down) ----
def grid_paths_memo(grid):
n = len(grid)
memo = {}
def paths(r, c):
if r < 0 or c < 0:
return 0
if grid[r][c] == '*': # ফাঁদ
return 0
if r == 0 and c == 0: # start (ফাঁদ না হলে এখানে আসে)
return 1
if (r, c) in memo:
return memo[(r, c)]
memo[(r, c)] = (paths(r - 1, c) + paths(r, c - 1)) % MOD
return memo[(r, c)]
if grid[0][0] == '*' or grid[n - 1][n - 1] == '*':
return 0
return paths(n - 1, n - 1)
# ---- way 2: tabulation (bottom-up) ----
def grid_paths_tab(grid):
n = len(grid)
if grid[0][0] == '*':
return 0
dp = [[0] * n for _ in range(n)]
dp[0][0] = 1
for r in range(n):
for c in range(n):
if grid[r][c] == '*':
dp[r][c] = 0
continue
if r == 0 and c == 0:
continue # base, already 1
up = dp[r - 1][c] if r > 0 else 0
left = dp[r][c - 1] if c > 0 else 0
dp[r][c] = (up + left) % MOD
return dp[n - 1][n - 1]
# ---- tests ----
cases = [
([list("..."), list(".*."), list("...")], 2),
([list("..."), list("..."), list("...")], 6), # ফাঁদ নেই = Unique Paths 3x3
([list("*.."), list("..."), list("...")], 0), # start ফাঁদ
([list("..."), list("..."), list("..*")], 0), # end ফাঁদ
([list("."), ], 1), # 1x1 খালি
([list("*"), ], 0), # 1x1 ফাঁদ
([list(".*"), list("..")], 1), # 2x2, উপর-ডানে ফাঁদ
]
for grid, want in cases:
assert grid_paths_memo(grid) == want, (grid, grid_paths_memo(grid))
assert grid_paths_tab(grid) == want, (grid, grid_paths_tab(grid))
print("সব test pass!")
16. Line-by-line code explanation¶
grid_paths_memo:paths(r,c)valid path-সংখ্যা দেয়; বাইরে বা ফাঁদ → 0; start → 1;memorepeated কাজ এড়ায়; শুরুতেই start/end ফাঁদ হলে সরাসরি 0।grid_paths_tab:dp[0][0]=1base; প্রতি cell-এ ফাঁদ হলে 0 আরcontinue; নয়তো উপর+বাঁ (সীমানার বাইরে 0 ধরে)% MOD; answerdp[n-1][n-1]।- দুই version-ই একই guard — ফাঁদ মানে 0 — শুধু একটা recursion-চালিত, একটা loop-চালিত।
17. Output walkthrough¶
cases-এ সাতটা grid: মাঝে-ফাঁদ (2), ফাঁদহীন (6 = Unique Paths), start-ফাঁদ (0), end-ফাঁদ (0), 1×1 খালি (1), 1×1 ফাঁদ (0), আর একটা 2×2 যেখানে একটাই path বাঁচে (1)। প্রতিটার জন্য দুই function মিলতে হবে। সব pass হলে শেষে print: সব test pass!।
18. Time complexity¶
- Memoization ও tabulation: O(n^2) — প্রতিটা cell একবার করে। (Naive recursion ছিল প্রায় O(2^(2n))।)
19. Space complexity¶
- Memoization: O(n^2) — dict + recursion stack।
- Tabulation: O(n^2) — 2D array।
(এক row rolling করলে O(n) space-ও সম্ভব, ফাঁদ-cell-এ সেই ঘর 0 করে দিয়ে।)
20. Common mistakes¶
- ফাঁদ cell-এ 0 না বসিয়ে শুধু skip করা — তখন পুরোনো garbage মান থেকে যায়, ভুল গণনা।
- start বা end নিজে ফাঁদ — এই case আলাদা না ধরলে ভুল 1 ফেরত আসতে পারে; শুরুতেই check করো।
- modulo নিতে ভুলে যাওয়া — CSES-এ বড় grid-এ উত্তর বিশাল, modulo ছাড়া wrong answer।
21. Which basic problem this inherits from¶
ভিত্তি: #7 Unique Paths-এর grid counting, একটা ফাঁদ-guard যোগ করা। ../state-transition-thinking.md-এর "Problem 4 — Grid Paths" section-এ obstacle version-এর কথা সরাসরি বলা ("cell-টা blocked হলে dp[r][c] = 0")।
22. Similar harder problems¶
- Minimum Path Sum (counting-এর বদলে min cost) — https://leetcode.com/problems/minimum-path-sum/ (এই tracker-এর #9)
- Unique Paths II (একই obstacle, LeetCode রূপ) — https://leetcode.com/problems/unique-paths-ii/
- Dungeon Game (grid DP, পেছন থেকে ভরা) — https://leetcode.com/problems/dungeon-game/
23. Pattern learned¶
Grid DP with obstacles: পরিচিত grid recurrence (উপর + বাঁ) নাও, তারপর blocked cell-এ dp = 0 বসিয়ে constraint চাপাও। সেই 0 downstream-এ আপনিই ছড়ায়। "পরিচিত recurrence + ছোট guard" — DP-তে বহুবার লাগবে।
24. Final summary¶
ভবিষ্যতের আমাকে: "Grid Paths = Unique Paths + একটা if (ফাঁদ হলে 0)।" State dp[r][c] = (r,c)-তে valid path, transition উপর+বাঁ কিন্তু ফাঁদ-এ 0, base dp[0][0]=1 (ফাঁদ না হলে), modulo নিতে ভুলো না। মনে রেখো: একটা constraint প্রায়ই মানে একটা পরিচিত DP-তে একটা ছোট guard বসানো।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।