৫টি সহজ BFS/DFS কোডিং প্রবলেম ও সমাধান (Java)
আগের ফাইলে ছিল Tree — এবার BFS/DFS-এর আসল খেলার মাঠ: Grid ও Graph। ইন্টারভিউতে graph traversal প্রবলেমের প্রায় সবই এই ৫টি প্যাটার্নের রূপভেদ।
প্রবলেম ১: Number of Islands (LeetCode 200) — Grid DFS
প্রশ্ন: একটি 2D grid-এ '1' = ভূমি, '0' = পানি। কতগুলো দ্বীপ (island) আছে বের করুন। পাশাপাশি (উপর-নিচ-ডান-বাম) যুক্ত ভূমিগুলো একটি দ্বীপ।
চিন্তার ধাপ: পুরো grid ঘুরুন। যখনই একটি '1' পাবেন — এটি একটি নতুন দ্বীপ, counter বাড়ান, আর DFS দিয়ে ওই দ্বীপের সব ভূমি "ডুবিয়ে দিন" ('0' করে দিন), যাতে একই দ্বীপ দ্বিতীয়বার গোনা না হয়। Grid নিজেই visited-এর কাজ করছে — আলাদা visited array লাগছে না।
সমাধান:
public int numIslands(char[][] grid) {
int count = 0;
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[0].length; c++) {
if (grid[r][c] == '1') {
count++;
sink(grid, r, c); // পুরো দ্বীপটি ডুবিয়ে দিন
}
}
}
return count;
}
private void sink(char[][] grid, int r, int c) {
// সীমার বাইরে বা পানি হলে থামুন
if (r < 0 || r >= grid.length || c < 0 || c >= grid[0].length
|| grid[r][c] == '0') {
return;
}
grid[r][c] = '0'; // visited হিসেবে চিহ্নিত
sink(grid, r + 1, c); // নিচ
sink(grid, r - 1, c); // উপর
sink(grid, r, c + 1); // ডান
sink(grid, r, c - 1); // বাম
}
Complexity: Time O(m×n) — প্রতিটি cell সর্বোচ্চ একবার sink হয়। Space O(m×n) worst case (recursion stack, পুরো grid একটাই দ্বীপ হলে)।
ইন্টারভিউ টিপ: "Input mutate করা কি ঠিক?" — জিজ্ঞেস করুন। আপত্তি থাকলে আলাদা boolean[][] visited ব্যবহার করুন। এই একটি প্রশ্ন করাই senior signal।
প্রবলেম ২: Flood Fill (LeetCode 733) — Grid DFS
প্রশ্ন: Paint bucket tool-এর মতো — একটি pixel (sr, sc) থেকে শুরু করে তার সাথে যুক্ত একই রঙের সব pixel-কে নতুন রঙে রাঙান।
চিন্তার ধাপ: Number of Islands-এর ছোট ভাই। শুরুর pixel-এর রঙ মনে রাখুন, তারপর DFS — যেখানেই ওই পুরনো রঙ পাবেন, নতুন রঙ বসান।
সমাধান:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
int oldColor = image[sr][sc];
if (oldColor != newColor) { // এই চেকটি না দিলে infinite loop!
fill(image, sr, sc, oldColor, newColor);
}
return image;
}
private void fill(int[][] image, int r, int c, int oldColor, int newColor) {
if (r < 0 || r >= image.length || c < 0 || c >= image[0].length
|| image[r][c] != oldColor) {
return;
}
image[r][c] = newColor;
fill(image, r + 1, c, oldColor, newColor);
fill(image, r - 1, c, oldColor, newColor);
fill(image, r, c + 1, oldColor, newColor);
fill(image, r, c - 1, oldColor, newColor);
}
Complexity: Time O(m×n), Space O(m×n) recursion stack।
ইন্টারভিউ টিপ: oldColor == newColor হলে কিছু না করেই return — এই edge case মিস করলে infinite recursion → StackOverflowError। Interviewer-রা এই case টি ইচ্ছা করে test-এ রাখেন।
প্রবলেম ৩: Rotting Oranges (LeetCode 994) — Multi-source BFS
প্রশ্ন: Grid-এ 2 = পচা কমলা, 1 = তাজা কমলা, 0 = খালি। প্রতি মিনিটে পচা কমলার পাশের তাজা কমলাগুলো পচে যায়। সব কমলা পচতে কত মিনিট লাগবে? সম্ভব না হলে -1।
চিন্তার ধাপ: "সবচেয়ে কম সময়/ধাপ" শুনলেই BFS। বিশেষত্ব: একাধিক উৎস — সব পচা কমলা একসাথে queue-তে দিয়ে শুরু করুন (multi-source BFS)। প্রতিটি BFS level = ১ মিনিট। Tree-র level order traversal-এর সেই levelSize snapshot প্যাটার্নটিই এখানে কাজে লাগে।
সমাধান:
public int orangesRotting(int[][] grid) {
Queue<int[]> queue = new LinkedList<>();
int fresh = 0;
// সব পচা কমলা queue-তে, তাজা কমলা গণনা
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[0].length; c++) {
if (grid[r][c] == 2) queue.offer(new int[]{r, c});
else if (grid[r][c] == 1) fresh++;
}
}
if (fresh == 0) return 0; // পচানোর কিছুই নেই
int minutes = 0;
int[][] dirs = {{1,0}, {-1,0}, {0,1}, {0,-1}};
while (!queue.isEmpty() && fresh > 0) {
int levelSize = queue.size(); // এই মিনিটে যারা পচাবে
for (int i = 0; i < levelSize; i++) {
int[] cell = queue.poll();
for (int[] d : dirs) {
int nr = cell[0] + d[0], nc = cell[1] + d[1];
if (nr >= 0 && nr < grid.length && nc >= 0 && nc < grid[0].length
&& grid[nr][nc] == 1) {
grid[nr][nc] = 2; // পচে গেল = visited
fresh--;
queue.offer(new int[]{nr, nc});
}
}
}
minutes++;
}
return fresh == 0 ? minutes : -1;
}
Complexity: Time O(m×n), Space O(m×n)।
ইন্টারভিউ টিপ: dirs array দিয়ে চার দিক লেখা — চারটি আলাদা if-এর বদলে — কোড ছোট ও ভুলমুক্ত রাখে। আর "কেন DFS নয়?" — কারণ DFS shortest path/minimum time দেয় না, BFS level-by-level ছড়ায় বলেই প্রতি level = ১ মিনিট কাজ করে।
প্রবলেম ৪: Cycle Detection in Undirected Graph — DFS
প্রশ্ন: n টি node (0 থেকে n-1) ও edge list দেওয়া আছে। Graph-এ cycle আছে কিনা বের করুন। (এটিই LeetCode 261 "Graph Valid Tree"-এর মূল অংশ।)
চিন্তার ধাপ: Undirected graph-এ DFS চালান। যদি এমন কোনো visited node-এ পৌঁছান যেটি আপনার সরাসরি parent নয় — cycle পাওয়া গেছে। Parent-কে বাদ দিতে হবে, কারণ undirected edge-এ A→B গেলে B থেকে A তো দেখা যাবেই — সেটা cycle নয়।
সমাধান:
public boolean hasCycle(int n, int[][] edges) {
// Adjacency list তৈরি
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) {
adj.get(e[0]).add(e[1]);
adj.get(e[1]).add(e[0]); // undirected: দুই দিকেই
}
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) { // disconnected graph-ও সামলানো
if (!visited[i]) {
if (dfs(adj, visited, i, -1)) return true;
}
}
return false;
}
private boolean dfs(List<List<Integer>> adj, boolean[] visited,
int node, int parent) {
visited[node] = true;
for (int neighbor : adj.get(node)) {
if (!visited[neighbor]) {
if (dfs(adj, visited, neighbor, node)) return true;
} else if (neighbor != parent) {
return true; // visited কিন্তু parent নয় → cycle!
}
}
return false;
}
Complexity: Time O(V + E), Space O(V + E)।
ইন্টারভিউ টিপ: Follow-up প্রায় নিশ্চিত — "directed graph হলে?" উত্তর: parent trick কাজ করবে না; তিন রঙ (white/gray/black) বা recursion stack tracking লাগবে — gray (বর্তমান path-এ থাকা) node-এ ফিরলেই cycle। আরেকটি বিকল্প: Union-Find — বলতে পারলেই bonus point।
প্রবলেম ৫: Shortest Path in Binary Matrix (LeetCode 1091) — BFS
প্রশ্ন: n×n grid-এ 0 = চলা যায়, 1 = ব্লক। উপরের-বাম কোণ থেকে নিচের-ডান কোণ পর্যন্ত সবচেয়ে ছোট path-এর দৈর্ঘ্য কত (৮ দিকেই চলা যায়)? পথ না থাকলে -1।
চিন্তার ধাপ: Unweighted grid/graph-এ shortest path মানেই BFS — DFS এখানে ভুল উত্তর দেবে। এখানে ৮ দিক (কোণাকুণিসহ)। Path length queue-র ভেতরে বহন করুন অথবা level গুনুন।
সমাধান:
public int shortestPathBinaryMatrix(int[][] grid) {
int n = grid.length;
if (grid[0][0] == 1 || grid[n-1][n-1] == 1) return -1;
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[]{0, 0, 1}); // {row, col, pathLength}
grid[0][0] = 1; // visited চিহ্নিত (queue-তে ঢোকানোর সময়েই!)
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int r = cell[0], c = cell[1], dist = cell[2];
if (r == n - 1 && c == n - 1) return dist; // গন্তব্যে পৌঁছে গেছি
for (int[] d : dirs) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < n && nc >= 0 && nc < n && grid[nr][nc] == 0) {
grid[nr][nc] = 1; // ঢোকানোর সময়েই visited
queue.offer(new int[]{nr, nc, dist + 1});
}
}
}
return -1;
}
Complexity: Time O(n²), Space O(n²)।
ইন্টারভিউ টিপ: সবচেয়ে সূক্ষ্ম ভুল — visited চিহ্নিত করা queue থেকে বের করার সময়, ঢোকানোর সময় নয়। তাতে একই cell বহুবার queue-তে ঢুকে TLE হয়। Enqueue করার মুহূর্তেই mark করুন। Follow-up: "প্রতিটি ঘরে ভিন্ন cost থাকলে?" → তখন BFS নয়, Dijkstra (PriorityQueue) — এই এক লাইনের উত্তর জানা থাকলেই যথেষ্ট।
BFS vs DFS — কোনটি কখন?
| পরিস্থিতি | ব্যবহার করুন |
|---|---|
| Shortest path / minimum steps / সময় (unweighted) | BFS |
| সব ঘর/অঞ্চল ঘুরে দেখা, connected component, flood fill | DFS (কোড ছোট) বা BFS — দুটোই চলে |
| Cycle detection | DFS (parent/color trick) |
| Level অনুযায়ী প্রসেসিং, multi-source ছড়ানো | BFS + levelSize snapshot |
| খুব গভীর grid/graph (stack overflow ঝুঁকি) | BFS বা iterative DFS |
মূল প্যাটার্ন — মনে রাখুন
১. Grid = graph: প্রতিটি cell একটি node, পাশের cell গুলো neighbor। dirs array দিয়ে দিক সামলান।
২. Visited অপরিহার্য — grid mutate করে অথবা আলাদা array-তে। BFS-এ mark করুন enqueue-এর সময়েই।
৩. "Minimum" শব্দ দেখলেই BFS, "সব ঘুরে দেখা/গোনা" হলে DFS।
৪. Boundary check প্রথমে — r < 0 || r >= rows || ... — এই লাইনটি প্রতিটি সমাধানে এক ছাঁচে লেখা অভ্যাস করুন।
পরবর্তী ধাপ: এগুলো আয়ত্তে এলে medium-এ যান — Course Schedule (207, topological sort), Clone Graph (133), Word Ladder (127), Pacific Atlantic Water Flow (417)।