|
| 1 | +--- |
| 2 | +id: out-of-boundary-paths |
| 3 | +title: Out of Boundary Paths |
| 4 | +sidebar_label: 0576 - Out of Boundary Paths |
| 5 | +tags: |
| 6 | + - Memoization |
| 7 | + - Dynamic Programming |
| 8 | + - Recursion |
| 9 | +description: "This is a solution to the Out of Boundary Paths problem on LeetCode." |
| 10 | +--- |
| 11 | + |
| 12 | +## Problem Description |
| 13 | + |
| 14 | +There is an `m x n` grid with a ball. The ball is initially at the position `[startRow, startColumn]`. You are allowed to move the ball to one of the four adjacent cells in the grid (possibly out of the grid crossing the grid boundary). You can apply **at most** `maxMove` moves to the ball. |
| 15 | + |
| 16 | +Given the five integers `m`, `n`, `maxMove`, `startRow`, `startColumn`, return the number of paths to move the ball out of the grid boundary. Since the answer can be very large, return it modulo $10^9 + 7$. |
| 17 | + |
| 18 | +### Examples |
| 19 | + |
| 20 | +**Example 1:** |
| 21 | + |
| 22 | + |
| 23 | +``` |
| 24 | +Input: m = 2, n = 2, maxMove = 2, startRow = 0, startColumn = 0 |
| 25 | +Output: 6 |
| 26 | +``` |
| 27 | + |
| 28 | +**Example 2:** |
| 29 | + |
| 30 | + |
| 31 | +``` |
| 32 | +Input: m = 1, n = 3, maxMove = 3, startRow = 0, startColumn = 1 |
| 33 | +Output: 12 |
| 34 | +``` |
| 35 | + |
| 36 | +### Constraints |
| 37 | + |
| 38 | +- `1 <= m, n <= 50` |
| 39 | +- `0 <= maxMove <= 50` |
| 40 | +- `0 <= startRow < m` |
| 41 | +- `0 <= startColumn < n` |
| 42 | + |
| 43 | +## Solution for Out of Boundary Paths |
| 44 | + |
| 45 | +### Approach 1: Brute Force [Time Limit Exceeded] |
| 46 | +#### Algorithm |
| 47 | + |
| 48 | +In the brute force approach, we try to take one step in every direction and decrement the number of pending moves for each step taken. Whenever we reach out of the boundary while taking the steps, we deduce that one extra path is available to take the ball out. |
| 49 | + |
| 50 | +In order to implement the same, we make use of a recursive function `findPaths(m,n,N,i,j)` which takes the current number of moves(N) along with the current position(i,j) as some of the parameters and returns the number of moves possible to take the ball out with the current pending moves from the current position. Now, we take a step in every direction and update the corresponding indices involved along with the current number of pending moves. |
| 51 | + |
| 52 | +Further, if we run out of moves at any moment, we return a 0 indicating that the current set of moves doesn't take the ball out of boundary. |
| 53 | + |
| 54 | +## Code in Different Languages |
| 55 | + |
| 56 | +<Tabs> |
| 57 | +<TabItem value="cpp" label="C++"> |
| 58 | + <SolutionAuthor name="@Shreyash3087"/> |
| 59 | + |
| 60 | +```cpp |
| 61 | +class Solution { |
| 62 | +public: |
| 63 | + int findPaths(int m, int n, int N, int i, int j) { |
| 64 | + if (i == m || j == n || i < 0 || j < 0) return 1; |
| 65 | + if (N == 0) return 0; |
| 66 | + return findPaths(m, n, N - 1, i - 1, j) |
| 67 | + + findPaths(m, n, N - 1, i + 1, j) |
| 68 | + + findPaths(m, n, N - 1, i, j - 1) |
| 69 | + + findPaths(m, n, N - 1, i, j + 1); |
| 70 | + } |
| 71 | +}; |
| 72 | + |
| 73 | + |
| 74 | +``` |
| 75 | +</TabItem> |
| 76 | +<TabItem value="java" label="Java"> |
| 77 | + <SolutionAuthor name="@Shreyash3087"/> |
| 78 | +
|
| 79 | +```java |
| 80 | +class Solution { |
| 81 | + public int findPaths(int m, int n, int N, int i, int j) { |
| 82 | + if (i == m || j == n || i < 0 || j < 0) return 1; |
| 83 | + if (N == 0) return 0; |
| 84 | + return findPaths(m, n, N - 1, i - 1, j) |
| 85 | + + findPaths(m, n, N - 1, i + 1, j) |
| 86 | + + findPaths(m, n, N - 1, i, j - 1) |
| 87 | + + findPaths(m, n, N - 1, i, j + 1); |
| 88 | + } |
| 89 | +} |
| 90 | +``` |
| 91 | + |
| 92 | +</TabItem> |
| 93 | +<TabItem value="python" label="Python"> |
| 94 | + <SolutionAuthor name="@Shreyash3087"/> |
| 95 | + |
| 96 | +```python |
| 97 | +class Solution: |
| 98 | + def findPaths(self, m: int, n: int, N: int, i: int, j: int) -> int: |
| 99 | + if i == m or j == n or i < 0 or j < 0: |
| 100 | + return 1 |
| 101 | + if N == 0: |
| 102 | + return 0 |
| 103 | + return (self.findPaths(m, n, N - 1, i - 1, j) + |
| 104 | + self.findPaths(m, n, N - 1, i + 1, j) + |
| 105 | + self.findPaths(m, n, N - 1, i, j - 1) + |
| 106 | + self.findPaths(m, n, N - 1, i, j + 1)) |
| 107 | + |
| 108 | +``` |
| 109 | +</TabItem> |
| 110 | +</Tabs> |
| 111 | + |
| 112 | +## Complexity Analysis |
| 113 | + |
| 114 | +### Time Complexity: $O(4^N)$ |
| 115 | + |
| 116 | +> **Reason**: Size of recursion tree will be $4^N$. Here, N refers to the number of moves allowed. |
| 117 | + |
| 118 | +### Space Complexity: $O(N)$ |
| 119 | + |
| 120 | +> **Reason**: The depth of the recursion tree can go upto N. |
| 121 | + |
| 122 | +### Approach 2: Recursion with Memoization |
| 123 | +#### Algorithm |
| 124 | +In the brute force approach, while going through the various branches of the recursion tree, we could reach the same position with the same number of moves left. |
| 125 | + |
| 126 | +Thus, a lot of redundant function calls are made with the same set of parameters leading to a useless increase in runtime. We can remove this redundancy by making use of a memoization array, memo. memo[i][j][k] is used to store the number of possible moves leading to a path out of the boundary if the current position is given by the indices (i,j) and number of moves left is k. |
| 127 | + |
| 128 | +Thus, now if a function call with some parameters is repeated, the memo array will already contain valid values corresponding to that function call resulting in pruning of the search space. |
| 129 | + |
| 130 | +## Code in Different Languages |
| 131 | + |
| 132 | +<Tabs> |
| 133 | +<TabItem value="cpp" label="C++"> |
| 134 | + <SolutionAuthor name="@Shreyash3087"/> |
| 135 | + |
| 136 | +```cpp |
| 137 | +#include <vector> |
| 138 | +#include <cstring> // For memset |
| 139 | + |
| 140 | +class Solution { |
| 141 | +public: |
| 142 | + int M = 1000000007; |
| 143 | + |
| 144 | + int findPaths(int m, int n, int N, int i, int j) { |
| 145 | + std::vector<std::vector<std::vector<int>>> memo(m, std::vector<std::vector<int>>(n, std::vector<int>(N + 1, -1))); |
| 146 | + return findPaths(m, n, N, i, j, memo); |
| 147 | + } |
| 148 | + |
| 149 | +private: |
| 150 | + int findPaths(int m, int n, int N, int i, int j, std::vector<std::vector<std::vector<int>>>& memo) { |
| 151 | + if (i == m || j == n || i < 0 || j < 0) return 1; |
| 152 | + if (N == 0) return 0; |
| 153 | + if (memo[i][j][N] >= 0) return memo[i][j][N]; |
| 154 | + memo[i][j][N] = ( |
| 155 | + (findPaths(m, n, N - 1, i - 1, j, memo) + findPaths(m, n, N - 1, i + 1, j, memo)) % M + |
| 156 | + (findPaths(m, n, N - 1, i, j - 1, memo) + findPaths(m, n, N - 1, i, j + 1, memo)) % M |
| 157 | + ) % M; |
| 158 | + return memo[i][j][N]; |
| 159 | + } |
| 160 | +}; |
| 161 | + |
| 162 | +``` |
| 163 | +</TabItem> |
| 164 | +<TabItem value="java" label="Java"> |
| 165 | + <SolutionAuthor name="@Shreyash3087"/> |
| 166 | +
|
| 167 | +```java |
| 168 | +class Solution { |
| 169 | + int M = 1000000007; |
| 170 | +
|
| 171 | + public int findPaths(int m, int n, int N, int i, int j) { |
| 172 | + int[][][] memo = new int[m][n][N + 1]; |
| 173 | + for (int[][] l : memo) for (int[] sl : l) Arrays.fill(sl, -1); |
| 174 | + return findPaths(m, n, N, i, j, memo); |
| 175 | + } |
| 176 | +
|
| 177 | + public int findPaths(int m, int n, int N, int i, int j, int[][][] memo) { |
| 178 | + if (i == m || j == n || i < 0 || j < 0) return 1; |
| 179 | + if (N == 0) return 0; |
| 180 | + if (memo[i][j][N] >= 0) return memo[i][j][N]; |
| 181 | + memo[i][j][N] = ( |
| 182 | + (findPaths(m, n, N - 1, i - 1, j, memo) + findPaths(m, n, N - 1, i + 1, j, memo)) % M + |
| 183 | + (findPaths(m, n, N - 1, i, j - 1, memo) + findPaths(m, n, N - 1, i, j + 1, memo)) % M |
| 184 | + ) % M; |
| 185 | + return memo[i][j][N]; |
| 186 | + } |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +</TabItem> |
| 191 | +<TabItem value="python" label="Python"> |
| 192 | + <SolutionAuthor name="@Shreyash3087"/> |
| 193 | + |
| 194 | +```python |
| 195 | +class Solution: |
| 196 | + M = 1000000007 |
| 197 | + |
| 198 | + def findPaths(self, m: int, n: int, N: int, i: int, j: int) -> int: |
| 199 | + memo = [[[-1 for _ in range(N + 1)] for _ in range(n)] for _ in range(m)] |
| 200 | + return self.findPathsMemo(m, n, N, i, j, memo) |
| 201 | + |
| 202 | + def findPathsMemo(self, m: int, n: int, N: int, i: int, j: int, memo: list) -> int: |
| 203 | + if i == m or j == n or i < 0 or j < 0: |
| 204 | + return 1 |
| 205 | + if N == 0: |
| 206 | + return 0 |
| 207 | + if memo[i][j][N] >= 0: |
| 208 | + return memo[i][j][N] |
| 209 | + memo[i][j][N] = ( |
| 210 | + (self.findPathsMemo(m, n, N - 1, i - 1, j, memo) + self.findPathsMemo(m, n, N - 1, i + 1, j, memo)) % self.M + |
| 211 | + (self.findPathsMemo(m, n, N - 1, i, j - 1, memo) + self.findPathsMemo(m, n, N - 1, i, j + 1, memo)) % self.M |
| 212 | + ) % self.M |
| 213 | + return memo[i][j][N] |
| 214 | + |
| 215 | +``` |
| 216 | +</TabItem> |
| 217 | +</Tabs> |
| 218 | + |
| 219 | +## Complexity Analysis |
| 220 | + |
| 221 | +### Time Complexity: $O(mnN)$ |
| 222 | + |
| 223 | +> **Reason**: We need to fill the memo array once with dimensions m×n×N. Here, m, n refer to the number of rows and columns of the given grid respectively. N refers to the total number of allowed moves. |
| 224 | +
|
| 225 | +### Space Complexity: $O(mnN)$ |
| 226 | + |
| 227 | +> **Reason**: memo array of size m×n×N is used. |
| 228 | +
|
| 229 | +## References |
| 230 | + |
| 231 | +- **LeetCode Problem**: [Out of Boundary Paths](https://leetcode.com/problems/out-of-boundary-paths/description/) |
| 232 | + |
| 233 | +- **Solution Link**: [Out of Boundary Paths](https://leetcode.com/problems/out-of-boundary-paths/solutions/) |
0 commit comments