From 89014e59df0e96d15e27a88fdb80fdbe76d22b0c Mon Sep 17 00:00:00 2001 From: ranveersingh2718 Date: Sun, 14 Dec 2025 22:02:41 -0800 Subject: [PATCH 1/4] add number-of-distinct-islands.md article --- articles/number-of-distinct-islands.md | 391 +++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 articles/number-of-distinct-islands.md diff --git a/articles/number-of-distinct-islands.md b/articles/number-of-distinct-islands.md new file mode 100644 index 000000000..34ec31ee9 --- /dev/null +++ b/articles/number-of-distinct-islands.md @@ -0,0 +1,391 @@ +## 1. Brute Force + +::tabs-start + +```python +class Solution: + def numDistinctIslands(self, grid: List[List[int]]) -> int: + + def current_island_is_unique(): + for other_island in unique_islands: + if len(other_island) != len(current_island): + continue + for cell_1, cell_2 in zip(current_island, other_island): + if cell_1 != cell_2: + break + else: + return False + return True + + # Do a DFS to find all cells in the current island. + def dfs(row, col): + if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]): + return + if (row, col) in seen or not grid[row][col]: + return + seen.add((row, col)) + current_island.append((row - row_origin, col - col_origin)) + dfs(row + 1, col) + dfs(row - 1, col) + dfs(row, col + 1) + dfs(row, col - 1) + + # Repeatedly start DFS's as long as there are islands remaining. + seen = set() + unique_islands = [] + for row in range(len(grid)): + for col in range(len(grid[0])): + current_island = [] + row_origin = row + col_origin = col + dfs(row, col) + if not current_island or not current_island_is_unique(): + continue + unique_islands.append(current_island) + print(unique_islands) + return len(unique_islands) +``` + +```java +class Solution { + + private List> uniqueIslands = new ArrayList<>(); // All known unique islands. + private List currentIsland = new ArrayList<>(); // Current Island + private int[][] grid; // Input grid + private boolean[][] seen; // Cells that have been explored. + + public int numDistinctIslands(int[][] grid) { + this.grid = grid; + this.seen = new boolean[grid.length][grid[0].length]; + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[0].length; col++) { + dfs(row, col); + if (currentIsland.isEmpty()) { + continue; + } + // Translate the island we just found to the top left. + int minCol = grid[0].length - 1; + for (int i = 0; i < currentIsland.size(); i++) { + minCol = Math.min(minCol, currentIsland.get(i)[1]); + } + for (int[] cell : currentIsland) { + cell[0] -= row; + cell[1] -= minCol; + } + // If this island is unique, add it to the list. + if (currentIslandUnique()) { + uniqueIslands.add(currentIsland); + } + currentIsland = new ArrayList<>(); + } + } + return uniqueIslands.size(); + } + + private void dfs(int row, int col) { + if (row < 0 || col < 0 || row >= grid.length || col >= grid[0].length) return; + if (seen[row][col] || grid[row][col] == 0) return; + seen[row][col] = true; + currentIsland.add(new int[]{row, col}); + dfs(row + 1, col); + dfs(row - 1, col); + dfs(row, col + 1); + dfs(row, col - 1); + } + + private boolean currentIslandUnique() { + for (List otherIsland : uniqueIslands) { + if (currentIsland.size() != otherIsland.size()) { + continue; + } + if (equalIslands(currentIsland, otherIsland)) { + return false; + } + } + return true; + } + + private boolean equalIslands(List island1, List island2) { + for (int i = 0; i < island1.size(); i++) { + if (island1.get(i)[0] != island2.get(i)[0] || island1.get(i)[1] != island2.get(i)[1]) { + return false; + } + } + return true; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(M^2 \cdot N^2)$ +- Space complexity: $O(N \cdot M)$ + +> Where $M$ is the number of rows, and $N$ is the number of columns + +--- + +## 2. Hash By Local Coordinates + +::tabs-start + +```python +class Solution: + def numDistinctIslands(self, grid: List[List[int]]) -> int: + # Do a DFS to find all cells in the current island. + def dfs(row, col): + if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]): + return + if (row, col) in seen or not grid[row][col]: + return + seen.add((row, col)) + current_island.add((row - row_origin, col - col_origin)) + dfs(row + 1, col) + dfs(row - 1, col) + dfs(row, col + 1) + dfs(row, col - 1) + + # Repeatedly start DFS's as long as there are islands remaining. + seen = set() + unique_islands = set() + for row in range(len(grid)): + for col in range(len(grid[0])): + current_island = set() + row_origin = row + col_origin = col + dfs(row, col) + if current_island: + unique_islands.add(frozenset(current_island)) + + return len(unique_islands) +``` + +```java +class Solution { + + private int[][] grid; + private boolean[][] seen; + private Set> currentIsland; + private int currRowOrigin; + private int currColOrigin; + + private void dfs(int row, int col) { + if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length) { + return; + } + if (grid[row][col] == 0 || seen[row][col]) { + return; + } + seen[row][col] = true; + currentIsland.add(new Pair<>(row - currRowOrigin, col - currColOrigin)); + dfs(row + 1, col); + dfs(row - 1, col); + dfs(row, col + 1); + dfs(row, col - 1); + } + + public int numDistinctIslands(int[][] grid) { + this.grid = grid; + this.seen = new boolean[grid.length][grid[0].length]; + Set>> islands = new HashSet<>(); + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[0].length; col++) { + this.currentIsland = new HashSet<>(); + this.currRowOrigin = row; + this.currColOrigin = col; + dfs(row, col); + if (!currentIsland.isEmpty()) { + islands.add(currentIsland); + } + } + } + return islands.size(); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(M \cdot N)$ +- Space complexity: $O(M \cdot N)$ + +> Where $M$ is the number of rows, and $N$ is the number of columns + +--- + +## 3. Hash By Path Signature + +::tabs-start + +```python +class Solution: + def numDistinctIslands(self, grid: List[List[int]]) -> int: + # Do a DFS to find all cells in the current island. + def dfs(row, col, direction): + if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]): + return + if (row, col) in seen or not grid[row][col]: + return + seen.add((row, col)) + path_signature.append(direction) + dfs(row + 1, col, "D") + dfs(row - 1, col, "U") + dfs(row, col + 1, "R") + dfs(row, col - 1, "L") + path_signature.append("0") + + # Repeatedly start DFS's as long as there are islands remaining. + seen = set() + unique_islands = set() + for row in range(len(grid)): + for col in range(len(grid[0])): + path_signature = [] + dfs(row, col, "0") + if path_signature: + unique_islands.add(tuple(path_signature)) + + return len(unique_islands) +``` + +```java +class Solution { + + private int[][] grid; + private boolean[][] visited; + private StringBuffer currentIsland; + + public int numDistinctIslands(int[][] grid) { + this.grid = grid; + this.visited = new boolean[grid.length][grid[0].length]; + Set islands = new HashSet<>(); + for (int row = 0; row < grid.length; row++) { + for (int col = 0; col < grid[0].length; col++) { + currentIsland = new StringBuffer(); + dfs(row, col, '0'); + if (currentIsland.length() == 0) { + continue; + } + islands.add(currentIsland.toString()); + } + } + return islands.size(); + } + + private void dfs(int row, int col, char dir) { + if (row < 0 || col < 0 || row >= grid.length || col >= grid[0].length) { + return; + } + if (visited[row][col] || grid[row][col] == 0) { + return; + } + visited[row][col] = true; + currentIsland.append(dir); + dfs(row + 1, col, 'D'); + dfs(row - 1, col, 'U'); + dfs(row, col + 1, 'R'); + dfs(row, col - 1, 'L'); + currentIsland.append('0'); + } +} +``` + +```cpp +class Solution { +private: + vector>* grid; + vector> visited; + string currentIsland; + + void dfs(int row, int col, char dir) { + if (row < 0 || col < 0 || row >= grid->size() || col >= (*grid)[0].size()) { + return; + } + if (visited[row][col] || (*grid)[row][col] == 0) { + return; + } + visited[row][col] = true; + currentIsland += dir; + dfs(row + 1, col, 'D'); + dfs(row - 1, col, 'U'); + dfs(row, col + 1, 'R'); + dfs(row, col - 1, 'L'); + currentIsland += '0'; + } + +public: + int numDistinctIslands(vector>& grid) { + this->grid = &grid; + visited = vector>(grid.size(), vector(grid[0].size(), false)); + unordered_set islands; + + for (int row = 0; row < grid.size(); row++) { + for (int col = 0; col < grid[0].size(); col++) { + currentIsland = ""; + dfs(row, col, '0'); + if (currentIsland.empty()) { + continue; + } + islands.insert(currentIsland); + } + } + return islands.size(); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[][]} grid + * @return {number} + */ + numDistinctIslands(grid) { + this.grid = grid; + this.visited = Array.from({ length: grid.length }, () => + Array(grid[0].length).fill(false) + ); + const islands = new Set(); + + for (let row = 0; row < grid.length; row++) { + for (let col = 0; col < grid[0].length; col++) { + this.currentIsland = []; + this.dfs(row, col, '0'); + if (this.currentIsland.length === 0) { + continue; + } + islands.add(this.currentIsland.join('')); + } + } + return islands.size; + } + + dfs(row, col, dir) { + if (row < 0 || col < 0 || row >= this.grid.length || col >= this.grid[0].length) { + return; + } + if (this.visited[row][col] || this.grid[row][col] === 0) { + return; + } + this.visited[row][col] = true; + this.currentIsland.push(dir); + this.dfs(row + 1, col, 'D'); + this.dfs(row - 1, col, 'U'); + this.dfs(row, col + 1, 'R'); + this.dfs(row, col - 1, 'L'); + this.currentIsland.push('0'); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(M \cdot N)$ +- Space complexity: $O(M \cdot N)$ + +> Where $M$ is the number of rows, and $N$ is the number of columns From 3d17e9bd3e1064aa3c49bb27eda62f0e3c7a787d Mon Sep 17 00:00:00 2001 From: ranveersingh2718 Date: Sun, 14 Dec 2025 23:07:59 -0800 Subject: [PATCH 2/4] add parallel-courses.md article --- articles/parallel-courses.md | 544 +++++++++++++++++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 articles/parallel-courses.md diff --git a/articles/parallel-courses.md b/articles/parallel-courses.md new file mode 100644 index 000000000..90fb2d863 --- /dev/null +++ b/articles/parallel-courses.md @@ -0,0 +1,544 @@ +## 1. Breadth-First Search (Kahn's Algorithm) + +::tabs-start + +```python +class Solution: + def minimumSemesters(self, N: int, relations: List[List[int]]) -> int: + graph = {i: [] for i in range(1, N + 1)} + in_count = {i: 0 for i in range(1, N + 1)} # or in-degree + for start_node, end_node in relations: + graph[start_node].append(end_node) + in_count[end_node] += 1 + + queue = [] + # we use list here since we are not + # popping from the front in this code + for node in graph: + if in_count[node] == 0: + queue.append(node) + + step = 0 + studied_count = 0 + # start learning with BFS + while queue: + # start new semester + step += 1 + next_queue = [] + for node in queue: + studied_count += 1 + end_nodes = graph[node] + for end_node in end_nodes: + in_count[end_node] -= 1 + # if all prerequisite courses learned + if in_count[end_node] == 0: + next_queue.append(end_node) + queue = next_queue + + return step if studied_count == N else -1 +``` + +```java +class Solution { + public int minimumSemesters(int N, int[][] relations) { + int[] inCount = new int[N + 1]; // or indegree + List> graph = new ArrayList<>(N + 1); + for (int i = 0; i < N + 1; ++i) { + graph.add(new ArrayList()); + } + + for (int[] relation : relations) { + graph.get(relation[0]).add(relation[1]); + inCount[relation[1]]++; + } + + int step = 0; + int studiedCount = 0; + List bfsQueue = new ArrayList<>(); + for (int node = 1; node < N + 1; node++) { + if (inCount[node] == 0) { + bfsQueue.add(node); + } + } + + // start learning with BFS + while (!bfsQueue.isEmpty()) { + // start new semester + step++; + List nextQueue = new ArrayList<>(); + for (int node : bfsQueue) { + studiedCount++; + for (int endNode : graph.get(node)) { + inCount[endNode]--; + // if all prerequisite courses learned + if (inCount[endNode] == 0) { + nextQueue.add(endNode); + } + } + } + bfsQueue = nextQueue; + } + + // check if learn all courses + return studiedCount == N ? step : -1; + } +} +``` + +```cpp +class Solution { +public: + int minimumSemesters(int N, vector>& relations) { + vector inCount(N + 1, 0); // or indegree + vector> graph(N + 1); + for (auto& relation : relations) { + graph[relation[0]].push_back(relation[1]); + inCount[relation[1]]++; + } + + int step = 0; + int studiedCount = 0; + vector bfsQueue; + for (int node = 1; node < N + 1; node++) { + if (inCount[node] == 0) { + bfsQueue.push_back(node); + } + } + + // start learning with BFS + while (!bfsQueue.empty()) { + // start new semester + step++; + vector nextQueue; + for (auto& node : bfsQueue) { + studiedCount++; + for (auto& endNode : graph[node]) { + inCount[endNode]--; + // if all prerequisite courses learned + if (inCount[endNode] == 0) { + nextQueue.push_back(endNode); + } + } + } + bfsQueue = nextQueue; + } + + // check if learn all courses + return studiedCount == N ? step : -1; + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} n + * @param {number[][]} relations + * @return {number} + */ + minimumSemesters(n, relations) { + const graph = {}; + const inCount = {}; // or in-degree + + for (let i = 1; i <= n; i++) { + graph[i] = []; + inCount[i] = 0; + } + + for (const [startNode, endNode] of relations) { + graph[startNode].push(endNode); + inCount[endNode]++; + } + + let queue = []; + // we use list here since we are not + // popping from the front in this code + for (let node = 1; node <= n; node++) { + if (inCount[node] === 0) { + queue.push(node); + } + } + + let step = 0; + let studiedCount = 0; + // start learning with BFS + while (queue.length > 0) { + // start new semester + step++; + const nextQueue = []; + + for (const node of queue) { + studiedCount++; + const endNodes = graph[node]; + + for (const endNode of endNodes) { + inCount[endNode]--; + // if all prerequisite courses learned + if (inCount[endNode] === 0) { + nextQueue.push(endNode); + } + } + } + + queue = nextQueue; + } + + return studiedCount === n ? step : -1; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(N + E)$ +- Space complexity: $O(N + E)$ + +> Where $E$ is the length of `relations` and $N$ is the number of courses. + +--- + +## 2. Depth-First Search: Check for Cycles + Find Longest Path + +::tabs-start + +```python +class Solution: + def minimumSemesters(self, N: int, relations: List[List[int]]) -> int: + graph = {i: [] for i in range(1, N + 1)} + for start_node, end_node in relations: + graph[start_node].append(end_node) + + # check if the graph contains a cycle + visited = {} + + def dfs_check_cycle(node: int) -> bool: + # return True if graph has a cycle + if node in visited: + return visited[node] + else: + # mark as visiting + visited[node] = -1 + for end_node in graph[node]: + if dfs_check_cycle(end_node): + # we meet a cycle! + return True + # mark as visited + visited[node] = False + return False + + # if has cycle, return -1 + for node in graph.keys(): + if dfs_check_cycle(node): + return -1 + + # if no cycle, return the longest path + visited_length = {} + + def dfs_max_path(node: int) -> int: + # return the longest path (inclusive) + if node in visited_length: + return visited_length[node] + max_length = 1 + for end_node in graph[node]: + length = dfs_max_path(end_node) + max_length = max(length+1, max_length) + # store it + visited_length[node] = max_length + return max_length + + return max(dfs_max_path(node)for node in graph.keys()) +``` + +```java +class Solution { + public int minimumSemesters(int N, int[][] relations) { + List> graph = new ArrayList<>(N + 1); + for (int i = 0; i < N + 1; ++i) { + graph.add(new ArrayList()); + } + for (int[] relation : relations) { + graph.get(relation[0]).add(relation[1]); + } + // check if the graph contains a cycle + int[] visited = new int[N + 1]; + for (int node = 1; node < N + 1; node++) { + // if has cycle, return -1 + if (dfsCheckCycle(node, graph, visited) == -1) { + return -1; + } + } + + // if no cycle, return the longest path + int[] visitedLength = new int[N + 1]; + int maxLength = 1; + for (int node = 1; node < N + 1; node++) { + int length = dfsMaxPath(node, graph, visitedLength); + maxLength = Math.max(length, maxLength); + } + return maxLength; + } + + private int dfsCheckCycle(int node, List> graph, int[] visited) { + // return -1 if has a cycle + // return 1 if does not have any cycle + if (visited[node] != 0) { + return visited[node]; + } else { + // mark as visiting + visited[node] = -1; + } + for (int endNode : graph.get(node)) { + if (dfsCheckCycle(endNode, graph, visited) == -1) { + // we meet a cycle! + return -1; + } + } + // mark as visited + visited[node] = 1; + return 1; + } + + private int dfsMaxPath(int node, List> graph, int[] visitedLength) { + // return the longest path (inclusive) + if (visitedLength[node] != 0) { + return visitedLength[node]; + } + int maxLength = 1; + for (int endNode : graph.get(node)) { + int length = dfsMaxPath(endNode, graph, visitedLength); + maxLength = Math.max(length + 1, maxLength); + } + // store it + visitedLength[node] = maxLength; + return maxLength; + } +} +``` + +```cpp +class Solution { +public: + int minimumSemesters(int N, vector>& relations) { + vector> graph(N + 1); + for (auto& relation : relations) { + graph[relation[0]].push_back(relation[1]); + } + // check if the graph contains a cycle + vector visited(N + 1, 0); + for (int node = 1; node < N + 1; node++) { + // if has cycle, return -1 + if (dfsCheckCycle(node, graph, visited) == -1) { + return -1; + } + } + + // if no cycle, return the longest path + vector visitedLength(N + 1, 0); + int maxLength = 1; + for (int node = 1; node < N + 1; node++) { + int length = dfsMaxPath(node, graph, visitedLength); + maxLength = max(length, maxLength); + } + return maxLength; + } + +private: + int dfsCheckCycle(int node, vector>& graph, + vector& visited) { + // return -1 if has a cycle + // return 1 if does not have any cycle + if (visited[node] != 0) { + return visited[node]; + } else { + // mark as visiting + visited[node] = -1; + } + for (auto& endNode : graph[node]) { + if (dfsCheckCycle(endNode, graph, visited) == -1) { + // we meet a cycle! + return -1; + } + } + // mark as visited + visited[node] = 1; + return 1; + } + + int dfsMaxPath(int node, vector>& graph, + vector& visitedLength) { + // return the longest path (inclusive) + if (visitedLength[node] != 0) { + return visitedLength[node]; + } + int maxLength = 1; + for (auto& endNode : graph[node]) { + int length = dfsMaxPath(endNode, graph, visitedLength); + maxLength = max(length + 1, maxLength); + } + // store it + visitedLength[node] = maxLength; + return maxLength; + } +}; +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(N + E)$ +- Space complexity: $O(N + E)$ + +> Where $E$ is the length of `relations` and $N$ is the number of courses. + +--- + +## 3. Depth-First Search: Combine + +::tabs-start + +```python +class Solution: + def minimumSemesters(self, N: int, relations: List[List[int]]) -> int: + graph = {i: [] for i in range(1, N + 1)} + for start_node, end_node in relations: + graph[start_node].append(end_node) + + visited = {} + + def dfs(node: int) -> int: + # return the longest path (inclusive) + if node in visited: + return visited[node] + else: + # mark as visiting + visited[node] = -1 + + max_length = 1 + for end_node in graph[node]: + length = dfs(end_node) + # we meet a cycle! + if length == -1: + return -1 + else: + max_length = max(length+1, max_length) + # mark as visited + visited[node] = max_length + return max_length + + max_length = -1 + for node in graph.keys(): + length = dfs(node) + # we meet a cycle! + if length == -1: + return -1 + else: + max_length = max(length, max_length) + return max_length +``` + +```java +class Solution { + public int minimumSemesters(int N, int[][] relations) { + List> graph = new ArrayList<>(N + 1); + for (int i = 0; i < N + 1; ++i) { + graph.add(new ArrayList()); + } + for (int[] relation : relations) { + graph.get(relation[0]).add(relation[1]); + } + int[] visited = new int[N + 1]; + + int maxLength = 1; + for (int node = 1; node < N + 1; node++) { + int length = dfs(node, graph, visited); + // we meet a cycle! + if (length == -1) { + return -1; + } + maxLength = Math.max(length, maxLength); + } + return maxLength; + } + + private int dfs(int node, List> graph, int[] visited) { + // return the longest path (inclusive) + if (visited[node] != 0) { + return visited[node]; + } else { + // mark as visiting + visited[node] = -1; + } + int maxLength = 1; + for (int endNode : graph.get(node)) { + int length = dfs(endNode, graph, visited); + // we meet a cycle! + if (length == -1) { + return -1; + } + maxLength = Math.max(length + 1, maxLength); + } + // mark as visited + visited[node] = maxLength; + return maxLength; + } +} +``` + +```cpp +class Solution { +public: + int minimumSemesters(int N, vector>& relations) { + vector> graph(N + 1); + for (auto& relation : relations) { + graph[relation[0]].push_back(relation[1]); + } + + vector visited(N + 1, 0); + int maxLength = 1; + for (int node = 1; node < N + 1; node++) { + int length = dfs(node, graph, visited); + // we meet a cycle! + if (length == -1) { + return -1; + } + maxLength = max(length, maxLength); + } + return maxLength; + } + +private: + int dfs(int node, vector>& graph, vector& visited) { + // return the longest path (inclusive) + if (visited[node] != 0) { + return visited[node]; + } else { + // mark as visiting + visited[node] = -1; + } + int maxLength = 1; + for (auto& endNode : graph[node]) { + int length = dfs(endNode, graph, visited); + // we meet a cycle! + if (length == -1) { + return -1; + } + maxLength = max(length + 1, maxLength); + } + // mark as visited + visited[node] = maxLength; + return maxLength; + } +}; +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(N + E)$ +- Space complexity: $O(N + E)$ + +> Where $E$ is the length of `relations` and $N$ is the number of courses. From 86dc839fdde673c65ebb0885ce4740ec67b717bc Mon Sep 17 00:00:00 2001 From: ranveersingh2718 Date: Mon, 15 Dec 2025 00:20:39 -0800 Subject: [PATCH 3/4] add the-maze.md article --- articles/the-maze.md | 309 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 articles/the-maze.md diff --git a/articles/the-maze.md b/articles/the-maze.md new file mode 100644 index 000000000..a6a3a44eb --- /dev/null +++ b/articles/the-maze.md @@ -0,0 +1,309 @@ +## 1. Depth First Search + +::tabs-start + +```python +class Solution: + def dfs(self, m, n, maze, curr, destination, visit): + if visit[curr[0]][curr[1]]: + return False + if curr[0] == destination[0] and curr[1] == destination[1]: + return True + + visit[curr[0]][curr[1]] = True + dirX = [0, 1, 0, -1] + dirY = [-1, 0, 1, 0] + + for i in range(4): + r = curr[0] + c = curr[1] + # Move the ball in the chosen direction until it can. + while r >= 0 and r < m and c >= 0 and c < n and maze[r][c] == 0: + r += dirX[i] + c += dirY[i] + # Revert the last move to get the cell to which the ball rolls. + if self.dfs(m, n, maze, [r - dirX[i], c - dirY[i]], destination, visit): + return True + return False + + def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool: + m = len(maze) + n = len(maze[0]) + visit = [[False] * n for _ in range(m)] + return self.dfs(m, n, maze, start, destination, visit) +``` + +```java +class Solution { + public boolean dfs(int m, int n, int[][] maze, int[] curr, int[] destination, + boolean[][] visit) { + + if (visit[curr[0]][curr[1]]) { + return false; + } + + if (curr[0] == destination[0] && curr[1] == destination[1]) { + return true; + } + + visit[curr[0]][curr[1]] = true; + int[] dirX = {0, 1, 0, -1}; + int[] dirY = {-1, 0, 1, 0}; + + for (int i = 0; i < 4; i++) { + int r = curr[0], c = curr[1]; + // Move the ball in the chosen direction until it can. + while (r >= 0 && r < m && c >= 0 && c < n && maze[r][c] == 0) { + r += dirX[i]; + c += dirY[i]; + } + + // Revert the last move to get the cell to which the ball rolls. + if (dfs(m, n, maze, new int[]{r - dirX[i], c - dirY[i]}, destination, visit)) { + return true; + } + } + return false; + } + + public boolean hasPath(int[][] maze, int[] start, int[] destination) { + int m = maze.length; + int n = maze[0].length; + boolean[][] visit = new boolean[m][n]; + return dfs(m, n, maze, start, destination, visit); + } +} +``` + +```cpp +class Solution { +public: + bool dfs(int m, int n, vector>& maze, vector curr, vector& destination, + vector>& visit) { + if (visit[curr[0]][curr[1]]) { + return false; + } + if (curr[0] == destination[0] && curr[1] == destination[1]) { + return true; + } + + visit[curr[0]][curr[1]] = true; + vector dirX{0, 1, 0, -1}; + vector dirY{-1, 0, 1, 0}; + + for (int i = 0; i < 4; i++) { + int r = curr[0], c = curr[1]; + // Move the ball in the chosen direction until it can. + while (r >= 0 && r < m && c >= 0 && c < n && maze[r][c] == 0) { + r += dirX[i]; + c += dirY[i]; + } + // Revert the last move to get the cell to which the ball rolls. + if (dfs(m, n, maze, {r - dirX[i], c - dirY[i]}, destination, visit)) { + return true; + } + } + return false; + } + + bool hasPath(vector>& maze, vector& start, vector& destination) { + int m = maze.size(); + int n = maze[0].size(); + vector> visit(m, vector(n)); + return dfs(m, n, maze, start, destination, visit); + } +}; +``` + +```javascript +class Solution { + /** + * @param {number} m + * @param {number} n + * @param {number[][]} maze + * @param {number[]} curr + * @param {number[]} destination + * @param {boolean[][]} visit + * @return {boolean} + */ + dfs(m, n, maze, curr, destination, visit) { + if (visit[curr[0]][curr[1]]) { + return false; + } + if (curr[0] === destination[0] && curr[1] === destination[1]) { + return true; + } + visit[curr[0]][curr[1]] = true; + const dirX = [0, 1, 0, -1]; + const dirY = [-1, 0, 1, 0]; + for (let i = 0; i < 4; i++) { + let r = curr[0]; + let c = curr[1]; + // Move the ball in the chosen direction until it can. + while (r >= 0 && r < m && c >= 0 && c < n && maze[r][c] === 0) { + r += dirX[i]; + c += dirY[i]; + } + // Revert the last move to get the cell to which the ball rolls. + if (this.dfs(m, n, maze, [r - dirX[i], c - dirY[i]], destination, visit)) { + return true; + } + } + return false; + } + + /** + * @param {number[][]} maze + * @param {number[]} start + * @param {number[]} destination + * @return {boolean} + */ + hasPath(maze, start, destination) { + const m = maze.length; + const n = maze[0].length; + const visit = Array.from({ length: m }, () => Array(n).fill(false)); + return this.dfs(m, n, maze, start, destination, visit); + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(m \cdot n \cdot (m + n))$ +- Space complexity: $O(m \cdot n)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`. + +--- + +## 2. Breadth First Search + +::tabs-start + +```python +class Solution: + def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool: + m = len(maze) + n = len(maze[0]) + visit = [[False] * n for _ in range(m)] + queue = deque() + + queue.append(start) + visit[start[0]][start[1]] = True + dirX = [0, 1, 0, -1] + dirY = [-1, 0, 1, 0] + + while queue: + curr = queue.popleft() + if curr[0] == destination[0] and curr[1] == destination[1]: + return True + + for i in range(4): + r = curr[0] + c = curr[1] + # Move the ball in the chosen direction until it can. + while r >= 0 and r < m and c >= 0 and c < n and maze[r][c] == 0: + r += dirX[i] + c += dirY[i] + # Revert the last move to get the cell to which the ball rolls. + r -= dirX[i] + c -= dirY[i] + if not visit[r][c]: + queue.append([r, c]) + visit[r][c] = True + return False +``` + +```java +class Solution { + public boolean hasPath(int[][] maze, int[] start, int[] destination) { + int m = maze.length; + int n = maze[0].length; + boolean[][] visit = new boolean[m][n]; + int[] dirX = {0, 1, 0, -1}; + int[] dirY = {-1, 0, 1, 0}; + + Queue queue = new LinkedList<>(); + queue.offer(start); + visit[start[0]][start[1]] = true; + + while (!queue.isEmpty()) { + int[] curr = queue.poll(); + if (curr[0] == destination[0] && curr[1] == destination[1]) { + return true; + } + for (int i = 0; i < 4; i++) { + int r = curr[0]; + int c = curr[1]; + // Move the ball in the chosen direction until it can. + while (r >= 0 && r < m && c >= 0 && c < n && maze[r][c] == 0) { + r += dirX[i]; + c += dirY[i]; + } + // Revert the last move to get the cell to which the ball rolls. + r -= dirX[i]; + c -= dirY[i]; + if (!visit[r][c]) { + queue.offer(new int[]{r, c}); + visit[r][c] = true; + } + } + } + return false; + } +} +``` + +```cpp +class Solution { +public: + bool hasPath(vector>& maze, vector& start, vector& destination) { + int m = maze.size(); + int n = maze[0].size(); + vector> visit(m, vector(n, false)); + vector dirX{0, 1, 0, -1}; + vector dirY{-1, 0, 1, 0}; + + queue> q; + q.push(start); + visit[start[0]][start[1]] = true; + + while (!q.empty()) { + vector curr = q.front(); + q.pop(); + if (curr[0] == destination[0] && curr[1] == destination[1]) { + return true; + } + + for (int i = 0; i < 4; i++) { + int r = curr[0]; + int c = curr[1]; + // Move the ball in the chosen direction until it can. + while (r >= 0 && r < m && c >= 0 && c < n && maze[r][c] == 0) { + r += dirX[i]; + c += dirY[i]; + } + // Revert the last move to get the cell to which the ball rolls. + r -= dirX[i]; + c -= dirY[i]; + if (!visit[r][c]) { + q.push({r, c}); + visit[r][c] = true; + } + } + } + return false; + } +}; +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(m \cdot n \cdot (m + n))$ +- Space complexity: $O(m \cdot n)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`. From 94bc89db64db3bb90ea0f3d6b28f01a3c728d7ac Mon Sep 17 00:00:00 2001 From: ranveersingh2718 Date: Mon, 15 Dec 2025 01:42:52 -0800 Subject: [PATCH 4/4] add the-maze-ii.md article --- articles/the-maze-ii.md | 363 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) create mode 100644 articles/the-maze-ii.md diff --git a/articles/the-maze-ii.md b/articles/the-maze-ii.md new file mode 100644 index 000000000..281ab125f --- /dev/null +++ b/articles/the-maze-ii.md @@ -0,0 +1,363 @@ +## 1. Depth First Search + +::tabs-start + +```java +class Solution { + public int shortestDistance(int[][] maze, int[] start, int[] dest) { + int[][] distance = new int[maze.length][maze[0].length]; + for (int[] row: distance) + Arrays.fill(row, Integer.MAX_VALUE); + distance[start[0]][start[1]] = 0; + dfs(maze, start, distance); + return distance[dest[0]][dest[1]] == Integer.MAX_VALUE ? -1 : distance[dest[0]][dest[1]]; + } + + public void dfs(int[][] maze, int[] start, int[][] distance) { + int[][] dirs={{0,1}, {0,-1}, {-1,0}, {1,0}}; + for (int[] dir: dirs) { + int x = start[0] + dir[0]; + int y = start[1] + dir[1]; + int count = 0; + while (x >= 0 && y >= 0 && x < maze.length && y < maze[0].length && maze[x][y] == 0) { + x += dir[0]; + y += dir[1]; + count++; + } + if (distance[start[0]][start[1]] + count < distance[x - dir[0]][y - dir[1]]) { + distance[x - dir[0]][y - dir[1]] = distance[start[0]][start[1]] + count; + dfs(maze, new int[]{x - dir[0],y - dir[1]}, distance); + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(m \cdot n \cdot max(m,n))$ +- Space complexity: $O(m \cdot n)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`. + +--- + +## 2. Breadth First Search + +::tabs-start + +```java +class Solution { + public int shortestDistance(int[][] maze, int[] start, int[] dest) { + int[][] distance = new int[maze.length][maze[0].length]; + for (int[] row: distance) + Arrays.fill(row, Integer.MAX_VALUE); + + distance[start[0]][start[1]] = 0; + int[][] dirs={{0, 1} ,{0, -1}, {-1, 0}, {1, 0}}; + Queue < int[] > queue = new LinkedList < > (); + queue.add(start); + while (!queue.isEmpty()) { + int[] s = queue.remove(); + for (int[] dir: dirs) { + int x = s[0] + dir[0]; + int y = s[1] + dir[1]; + int count = 0; + while (x >= 0 && y >= 0 && x < maze.length && y < maze[0].length && maze[x][y] == 0) { + x += dir[0]; + y += dir[1]; + count++; + } + + if (distance[s[0]][s[1]] + count < distance[x - dir[0]][y - dir[1]]) { + distance[x - dir[0]][y - dir[1]] = distance[s[0]][s[1]] + count; + queue.add(new int[] {x - dir[0], y - dir[1]}); + } + } + } + return distance[dest[0]][dest[1]] == Integer.MAX_VALUE ? -1 : distance[dest[0]][dest[1]]; + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(m \cdot n \cdot (m + n))$ +- Space complexity: $O(m \cdot n)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`. + +--- + +## 3. Dijkstra's Algorithm + +::tabs-start + +```java +class Solution { + public int shortestDistance(int[][] maze, int[] start, int[] dest) { + int[][] distance = new int[maze.length][maze[0].length]; + boolean[][] visited = new boolean[maze.length][maze[0].length]; + for (int[] row: distance) + Arrays.fill(row, Integer.MAX_VALUE); + + distance[start[0]][start[1]] = 0; + dijkstra(maze, distance, visited); + return distance[dest[0]][dest[1]] == Integer.MAX_VALUE ? -1 : distance[dest[0]][dest[1]]; + } + + public int[] minDistance(int[][] distance, boolean[][] visited) { + int[] min={-1,-1}; + int min_val = Integer.MAX_VALUE; + + for (int i = 0; i < distance.length; i++) { + for (int j = 0; j < distance[0].length; j++) { + if (!visited[i][j] && distance[i][j] < min_val) { + min = new int[] {i, j}; + min_val = distance[i][j]; + } + } + } + + return min; + } + + public void dijkstra(int[][] maze, int[][] distance, boolean[][] visited) { + int[][] dirs={{0,1},{0,-1},{-1,0},{1,0}}; + while (true) { + int[] s = minDistance(distance, visited); + if (s[0] < 0) + break; + visited[s[0]][s[1]] = true; + for (int[] dir: dirs) { + int x = s[0] + dir[0]; + int y = s[1] + dir[1]; + int count = 0; + while (x >= 0 && y >= 0 && x < maze.length && y < maze[0].length && maze[x][y] == 0) { + x += dir[0]; + y += dir[1]; + count++; + } + + if (distance[s[0]][s[1]] + count < distance[x - dir[0]][y - dir[1]]) { + distance[x - dir[0]][y - dir[1]] = distance[s[0]][s[1]] + count; + } + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O((mn)^2)$ +- Space complexity: $O(mn)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`. + +--- + +## 4. Dijkstra's Algorithm and Priority Queue + +::tabs-start + +```python +class Solution: + def shortestDistance(self, maze: List[List[int]], start: List[int], destination: List[int]) -> int: + distance = [[float('inf')] * len(maze[0]) for _ in range(len(maze))] + distance[start[0]][start[1]] = 0 + self.dijkstra(maze, start, distance) + return -1 if distance[destination[0]][destination[1]] == float('inf') else distance[destination[0]][destination[1]] + + def dijkstra(self, maze: List[List[int]], start: List[int], distance: List[List[int]]) -> None: + dirs = [[0, 1], [0, -1], [-1, 0], [1, 0]] + queue = [] + heapq.heappush(queue, (0, start[0], start[1])) # (distance, x, y) + + while queue: + dist, sx, sy = heapq.heappop(queue) + + if distance[sx][sy] < dist: + continue + + for dx, dy in dirs: + x, y = sx + dx, sy + dy + count = 0 + + while 0 <= x < len(maze) and 0 <= y < len(maze[0]) and maze[x][y] == 0: + x += dx + y += dy + count += 1 + + if distance[sx][sy] + count < distance[x - dx][y - dy]: + distance[x - dx][y - dy] = distance[sx][sy] + count + heapq.heappush(queue, (distance[x - dx][y - dy], x - dx, y - dy)) +``` + +```java +class Solution { + public int shortestDistance(int[][] maze, int[] start, int[] dest) { + int[][] distance = new int[maze.length][maze[0].length]; + + for (int[] row: distance) + Arrays.fill(row, Integer.MAX_VALUE); + + distance[start[0]][start[1]] = 0; + dijkstra(maze, start, distance); + return distance[dest[0]][dest[1]] == Integer.MAX_VALUE ? -1 : distance[dest[0]][dest[1]]; + } + + public void dijkstra(int[][] maze, int[] start, int[][] distance) { + int[][] dirs={{0,1},{0,-1},{-1,0},{1,0}}; + PriorityQueue < int[] > queue = new PriorityQueue < > ((a, b) -> a[2] - b[2]); + queue.offer(new int[]{start[0],start[1],0}); + + while (!queue.isEmpty()) { + int[] s = queue.poll(); + if(distance[s[0]][s[1]] < s[2]) + continue; + + for (int[] dir: dirs) { + int x = s[0] + dir[0]; + int y = s[1] + dir[1]; + int count = 0; + while (x >= 0 && y >= 0 && x < maze.length && y < maze[0].length && maze[x][y] == 0) { + x += dir[0]; + y += dir[1]; + count++; + } + + if (distance[s[0]][s[1]] + count < distance[x - dir[0]][y - dir[1]]) { + distance[x - dir[0]][y - dir[1]] = distance[s[0]][s[1]] + count; + queue.offer(new int[]{x - dir[0], y - dir[1], distance[x - dir[0]][y - dir[1]]}); + } + } + } + } +} +``` + +```cpp +class Solution { +public: + int shortestDistance(vector>& maze, vector& start, vector& destination) { + int m = maze.size(); + int n = maze[0].size(); + vector> distance(m, vector(n, INT_MAX)); + distance[start[0]][start[1]] = 0; + dijkstra(maze, start, distance); + return distance[destination[0]][destination[1]] == INT_MAX ? -1 : distance[destination[0]][destination[1]]; + } + +private: + void dijkstra(vector>& maze, vector& start, vector>& distance) { + int m = maze.size(); + int n = maze[0].size(); + vector> dirs = {{0, 1}, {0, -1}, {-1, 0}, {1, 0}}; + + // Min-heap: {distance, x, y} + priority_queue, vector>, greater>> pq; + pq.push({0, start[0], start[1]}); + + while (!pq.empty()) { + vector s = pq.top(); + pq.pop(); + int dist = s[0], sx = s[1], sy = s[2]; + + if (distance[sx][sy] < dist) + continue; + + for (auto& dir : dirs) { + int x = sx + dir[0]; + int y = sy + dir[1]; + int count = 0; + + while (x >= 0 && y >= 0 && x < m && y < n && maze[x][y] == 0) { + x += dir[0]; + y += dir[1]; + count++; + } + + if (distance[sx][sy] + count < distance[x - dir[0]][y - dir[1]]) { + distance[x - dir[0]][y - dir[1]] = distance[sx][sy] + count; + pq.push({distance[x - dir[0]][y - dir[1]], x - dir[0], y - dir[1]}); + } + } + } + } +}; +``` + +```javascript +class Solution { + /** + * @param {number[][]} maze + * @param {number[]} start + * @param {number[]} destination + * @return {number} + */ + shortestDistance(maze, start, destination) { + const m = maze.length; + const n = maze[0].length; + const distance = Array.from({ length: m }, () => Array(n).fill(Infinity)); + distance[start[0]][start[1]] = 0; + this.dijkstra(maze, start, distance); + return distance[destination[0]][destination[1]] === Infinity ? -1 : distance[destination[0]][destination[1]]; + } + + /** + * @param {number[][]} maze + * @param {number[]} start + * @param {number[][]} distance + * @return {void} + */ + dijkstra(maze, start, distance) { + const m = maze.length; + const n = maze[0].length; + const dirs = [[0, 1], [0, -1], [-1, 0], [1, 0]]; + + // @datastructures-js/priority-queue MinPriorityQueue implementation + const pq = new MinPriorityQueue((element) => element.dist); + pq.enqueue({ dist: 0, x: start[0], y: start[1] }); + + while (!pq.isEmpty()) { + const { dist, x: sx, y: sy } = pq.dequeue(); + + if (distance[sx][sy] < dist) + continue; + + for (const [dx, dy] of dirs) { + let x = sx + dx; + let y = sy + dy; + let count = 0; + + while (x >= 0 && y >= 0 && x < m && y < n && maze[x][y] === 0) { + x += dx; + y += dy; + count++; + } + + if (distance[sx][sy] + count < distance[x - dx][y - dy]) { + distance[x - dx][y - dy] = distance[sx][sy] + count; + pq.enqueue({ dist: distance[x - dx][y - dy], x: x - dx, y: y - dy }); + } + } + } + } +} +``` + +::tabs-end + +### Time & Space Complexity + +- Time complexity: $O(mn \cdot \log(mn))$ +- Space complexity: $O(m \cdot n)$ + +> Where $m$ and $n$ are the number of rows and columns in `maze`.