Skip to content

008 — Number of Islands

Source

  • Original source: Classic capstone interview problem (grid flood fill)
  • Platform: LeetCode — https://leetcode.com/problems/number-of-islands/
  • Topic: graphs + recursion (DFS)
  • Difficulty: Medium
  • Pattern: Grid flood fill — connected-component counting via DFS
  • Basic idea inherited from: এটা একটা cross-topic capstone — এখানে মেশে 09 graphs (grid-টাকে একটা implicit graph ধরা: প্রতিটা land cell একটা node, পাশের land cell-এর সাথে edge; connected component গোনা) আর 06 recursion (DFS দিয়ে একটা cell থেকে শুরু করে পুরো island ছেয়ে ফেলা — flood fill)। দুই tool জোড়া লাগলেই এই problem।

1. Problem in simple words

তোমাকে একটা grid (সারি-কলামের ছক) দেওয়া, যেখানে প্রতিটা ঘর হয় '1' (স্থল/land) নয় '0' (জল/water)। পাশাপাশি (উপর-নিচ-বাঁ-ডান, তির্যক না) থাকা land ঘরগুলো মিলে একটা island বানায়। তোমার কাজ: grid-এ মোট কতগুলো আলাদা island আছে গোনো।

grid:
1 1 0 0
1 1 0 0
0 0 1 0
0 0 0 1

island সংখ্যা = 3  (উপরে-বাঁয়ে একটা, মাঝে একটা, নিচে-ডানে একটা)

2. Which basic idea does this inherit from?

এই problem দুটো আগের chapter-এর tool জোড়া দেয়:

  • 09 graphs থেকে: grid-টা আসলে একটা লুকানো graph — প্রতিটা land cell একটা node, পাশের land cell-এর সাথে edge। তখন "কতগুলো island" = "কতগুলো connected component"।
  • 06 recursion থেকে: একটা land cell পেলে DFS দিয়ে তার চারপাশের সব যুক্ত land cell-এ ছড়িয়ে গিয়ে পুরো island-টা "ভিজিয়ে" (flood fill) ফেলা।

একা graph-চিন্তা দেয় component-counting framing; একা recursion দেয় ছড়িয়ে পড়ার কৌশল। দুটো মিললে পরিষ্কার flood fill।

3. What is the hidden math or logic?

লুকানো logic: connected components গোনা। প্রতিটা নতুন, এখনও-না-ছোঁয়া land cell একটা নতুন component-এর প্রতিনিধি। সেখান থেকে DFS করে পুরো component-কে "visited" চিহ্নিত করলে, পরে সেই cell-গুলো আর নতুন island শুরু করতে পারবে না। তাই কতবার আমরা নতুন DFS শুরু করি = island-এর সংখ্যা।

4. Which data structure is needed?

মূল tool: DFS recursion (06)। আলাদাভাবে visited চিহ্ন রাখতে হয় — হয় একটা আলাদা visited set/matrix, নয় (এখানে যেমন) ভিজিয়ে ফেলা cell-কে সরাসরি '0' করে দেওয়া (in-place marking)। implicit graph-টা নিজেই data structure (09)।

5. Why this data structure fits

grid-এ adjacency সরাসরি index দিয়ে পাওয়া যায় (r±1, c±1) — আলাদা adjacency list বানাতে হয় না, তাই DFS recursion-ই সবচেয়ে সহজ ও স্বাভাবিক tool। একটা cell ভিজিয়ে '0' করে দিলে সেটা আর গোনায় আসে না, তাই আলাদা visited structure-ও বাঁচে। এই in-place flood fill-ই recursion (06) আর grid-graph (09)-কে এখানে নিখুঁত করে তোলে।

6. Real-life analogy

ভাবো একটা মানচিত্রে কালি ছড়ানো। তুমি একটা শুকনো ডাঙায় এক ফোঁটা রঙ ফেললে — রঙ চারদিকে যত দূর ডাঙা আছে ততদূর ছড়িয়ে পুরো দ্বীপটা রাঙিয়ে দেয়, জল পেলে থেমে যায়। একটা দ্বীপ পুরো রাঙানো হলে তুমি এগিয়ে যাও পরের না-রাঙানো ডাঙা খুঁজতে — যতবার নতুন ফোঁটা ফেলতে হলো, ততগুলো দ্বীপ।

7. Visual explanation

ছোট grid-এ flood fill, X = এইমাত্র ভেজানো ('0' করা):

শুরু:        (0,0)-তে '1' পেলাম -> DFS শুরু, island গোনা = 1
1 1 0        X 1 0        X X 0        X X 0
1 0 0   ->   1 0 0   ->   X 0 0   ->   X 0 0   (এই island শেষ)
0 0 1        0 0 1        0 0 1        0 0 1

তারপর scan চলতে চলতে (2,2)-তে আরেকটা '1' -> DFS শুরু, island গোনা = 2
শেষ উত্তর = 2

8. Example input and output

Input :
1 1 0 0
1 1 0 0
0 0 1 0
0 0 0 1
Output: 3

Edge case 1 (পুরো জল): সব '0'        -> Output: 0
Edge case 2 (পুরো ডাঙা 1x3): 1 1 1   -> Output: 1
Edge case 3 (খালি grid): []          -> Output: 0

9. Brute force thinking

আসলে এই problem-এর "brute force"-ও একটা traversal-ই — কিন্তু সরল চিন্তা হলো: প্রতিটা cell ঘুরে দেখি, land পেলে তার চারপাশের সব যুক্ত land-কে আলাদা একটা visited set-এ চিহ্নিত করি, আর নতুন (অচিহ্নিত) land পেলেই counter বাড়াই।

visited = set()
count = 0
for প্রতিটা cell (r, c):
    if grid[r][c] == '1' and (r,c) not in visited:
        count += 1
        dfs(r, c)   # যুক্ত সব land visited-এ ঢোকাও

10. Why brute force is slow

এটা আসলে সঠিক ও যথেষ্ট দ্রুত (O(rows·cols)) — flood-fill problem-এ নিখুঁত brute force আর optimal প্রায় একই। "ধীর" version বলতে: যদি প্রতিটা cell থেকে বারবার পুরো grid ঘুরে যুক্ততা যাচাই করতে যেতাম (visited চিহ্ন না রেখে), তখন একই cell বহুবার দেখা হয়ে খরচ ফুলে যেত। visited-marking (বা in-place '0') সেই পুনরাবৃত্তি ঠেকায়।

11. Key observation

মূল observation: একটা island একবার পুরো ভিজিয়ে ফেললে, তার কোনো cell আর নতুন island শুরু করতে পারে না। তাই শুধু গুনে রাখলেই হয় — কতবার আমরা একটা টাটকা (এখনও '1') cell-এ DFS শুরু করি। এই এক চিন্তাই counting-টা সহজ করে।

12. Optimized intuition

পুরো grid scan করো। যখনই একটা '1' পাও যা এখনও ভেজানো হয়নি: island counter এক বাড়াও, আর সেখান থেকে DFS করে চারদিকের সব যুক্ত '1'-কে '0' করে দাও (ভিজিয়ে দাও)। scan শেষ হলে counter-ই island-সংখ্যা। in-place '0'-করা মানে আলাদা visited লাগে না।

13. Thinking tweak

মোচড়: "কোন cell কোন cell-এর সাথে যুক্ত তার পুরো map বানাব" ভাবার বদলে ভাবো "land পেলেই এক ফোঁটা রঙ ফেলে পুরো island মুছে দিই ('0' করি), আর কতবার ফোঁটা ফেলতে হলো গুনি।" component-counting একটা scan + flood fill-এ গলে যায়।

14. Step-by-step dry run

Input grid [["1","1"],["0","1"]]:

  1. scan (0,0)='1' → count = 1; DFS: (0,0)→'0', ডানে (0,1)='1'→'0', তার নিচে (1,1)='1'→'0'; চারপাশ আর '1' নেই → island শেষ
  2. scan (0,1) এখন '0' → skip
  3. scan (1,0)='0' → skip
  4. scan (1,1) এখন '0' → skip
  5. শেষ → return count = 1

15. Python solution

def num_islands(grid):
    if not grid or not grid[0]:
        return 0
    rows, cols = len(grid), len(grid[0])

    def dfs(r, c):
        # সীমার বাইরে বা জল হলে থামো
        if r < 0 or r >= rows or c < 0 or c >= cols or grid[r][c] != "1":
            return
        grid[r][c] = "0"              # এই land ভিজিয়ে দাও (visited)
        dfs(r + 1, c)                 # নিচ
        dfs(r - 1, c)                 # উপর
        dfs(r, c + 1)                 # ডান
        dfs(r, c - 1)                 # বাঁ

    count = 0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == "1":     # টাটকা land -> নতুন island
                count += 1
                dfs(r, c)             # পুরো island ভিজিয়ে দাও
    return count

# ---- tests ----
g1 = [["1","1","0","0"],
      ["1","1","0","0"],
      ["0","0","1","0"],
      ["0","0","0","1"]]
assert num_islands(g1) == 3

assert num_islands([["0","0"],["0","0"]]) == 0      # পুরো জল
assert num_islands([["1","1","1"]]) == 1            # একটানা ডাঙা
assert num_islands([]) == 0                         # খালি grid
assert num_islands([["1","0","1","0","1"]]) == 3    # বিচ্ছিন্ন ডাঙা
print("সব test pass!")

16. Line-by-line code explanation

  • if not grid or not grid[0]: return 0 — খালি grid edge case।
  • rows, cols = ... — সীমা ধরে রাখি, যাতে DFS-এ index-চেক করা যায়।
  • def dfs(r, c) — recursive flood fill (06 recursion)।
  • if ... or grid[r][c] != "1": return — boundary-র বাইরে গেলে বা জল/already-ভেজানো পেলে থামো — এটাই DFS-এর base case।
  • grid[r][c] = "0" — এই cell ভিজিয়ে দাও, যাতে আবার গোনা না হয় (in-place visited)।
  • চারটা dfs(...) — চার প্রতিবেশীতে ছড়াও (grid-graph-এর edge, 09)।
  • বাইরের double loop — প্রতিটা cell scan; টাটকা '1' পেলে count বাড়িয়ে নতুন DFS।
  • return count — মোট কতবার নতুন DFS শুরু হলো = island-সংখ্যা।

17. Output walkthrough

g1-এ scan করতে করতে প্রথম '1' (0,0)-তে → count 1, DFS উপরে-বাঁয়ের 2x2 ব্লক মুছে দেয়; পরে (2,2)-তে → count 2, একক cell মুছে যায়; শেষে (3,3)-তে → count 3। return 3 — assert pass। পুরো-জল, একটানা-ডাঙা, খালি, বিচ্ছিন্ন case-ও সঠিক। শেষে print: সব test pass!

18. Time complexity

O(rows · cols) — প্রতিটা cell সর্বোচ্চ একবার scan-এ আর একবার DFS-এ ছোঁয়া হয়।

19. Space complexity

O(rows · cols) worst case — recursion stack পুরো grid land হলে তত গভীর হতে পারে (06 recursion-এর hidden cost)। in-place marking-এ আলাদা visited লাগে না, তবু stack-টা থাকে।

20. Common mistakes

  • ভেজানো cell চিহ্নিত না করা ('0' না করা বা visited-এ না ঢোকানো) — তখন DFS অসীম লুপে পড়ে বা একই island বহুবার গোনে।
  • তির্যক (diagonal) প্রতিবেশীও যুক্ত ধরা — এই problem-এ শুধু 4-direction; 8-direction ভুল উত্তর দেয়।
  • boundary-চেক DFS-এর শুরুতে না রাখা — তখন index out of range crash।
  • খালি grid ([]) আলাদা না ভাবা — grid[0]-এ crash করতে পারে।

21. Which basic problem this inherits from

ভিত্তি: graph-এ connected component / DFS traversal (09 graphs, ../../09-graphs/) + recursion দিয়ে নিজেকে চার দিকে ডাকা (06 recursion, ../../06-recursion-and-backtracking/)। সাধারণ graph DFS-ই এখানে grid-এর উপর implicit-edge দিয়ে চলছে।

22. Similar harder problems

23. Pattern learned

Grid flood fill (connected components via DFS): grid-কে implicit graph ধরে, প্রতিটা টাটকা land থেকে DFS করে পুরো component ভিজিয়ে দাও আর কতবার শুরু করলে গুনো — component-counting-এর canonical রূপ। BFS দিয়েও একই কাজ হয়।

24. Final summary

ভবিষ্যতের আমাকে: 2D grid-এ "কতগুলো region / connected blob" শুনলেই flood fill (DFS/BFS) মনে করবে। মূল চাল — cell ভিজিয়ে দাও ('0' বা visited) যাতে দু'বার না গোনো, আর নতুন DFS-শুরুর সংখ্যা গোনো। 4 না 8 direction, সেটা clarify করতে ভুলো না। DFS recursion depth বড় হলে BFS-এ যাওয়ার কথাও বলতে পারো।


মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।