Skip to content

Commit d8fb657

Browse files
committed
Add Unique Paths problem with backtracking and DP solutions.
1 parent 863dbdb commit d8fb657

File tree

6 files changed

+224
-0
lines changed

6 files changed

+224
-0
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ a set of rules that precisely define a sequence of operations.
118118
* `B` [Tower of Hanoi](src/algorithms/uncategorized/hanoi-tower)
119119
* `B` [Square Matrix Rotation](src/algorithms/uncategorized/square-matrix-rotation) - in-place algorithm
120120
* `B` [Jump Game](src/algorithms/uncategorized/jump-game) - backtracking, dynamic programming (top-down + bottom-up) and greedy examples
121+
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) - backtracking and dynamic programming examples
121122
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
122123
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
123124

@@ -151,6 +152,7 @@ algorithm is an abstraction higher than a computer program.
151152
* **Dynamic Programming** - build up a solution using previously found sub-solutions
152153
* `B` [Fibonacci Number](src/algorithms/math/fibonacci)
153154
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
155+
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths)
154156
* `A` [Levenshtein Distance](src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences
155157
* `A` [Longest Common Subsequence](src/algorithms/sets/longest-common-subsequence) (LCS)
156158
* `A` [Longest Common Substring](src/algorithms/string/longest-common-substring)
@@ -166,6 +168,7 @@ algorithm is an abstraction higher than a computer program.
166168
if it satisfies all conditions, and only then continue generating subsequent solutions. Otherwise, backtrack, and go on a
167169
different path of finding a solution. Normally the DFS traversal of state-space is being used.
168170
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
171+
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths)
169172
* `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
170173
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
171174
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Unique Paths Problem
2+
3+
A robot is located at the top-left corner of a `m x n` grid
4+
(marked 'Start' in the diagram below).
5+
6+
The robot can only move either down or right at any point in
7+
time. The robot is trying to reach the bottom-right corner
8+
of the grid (marked 'Finish' in the diagram below).
9+
10+
How many possible unique paths are there?
11+
12+
![Unique Paths](https://leetcode.com/static/images/problemset/robot_maze.png)
13+
14+
## Examples
15+
16+
**Example #1**
17+
18+
```
19+
Input: m = 3, n = 2
20+
Output: 3
21+
Explanation:
22+
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
23+
1. Right -> Right -> Down
24+
2. Right -> Down -> Right
25+
3. Down -> Right -> Right
26+
```
27+
28+
**Example #2**
29+
30+
```
31+
Input: m = 7, n = 3
32+
Output: 28
33+
```
34+
35+
## Algorithms
36+
37+
### Backtracking
38+
39+
First thought that might came to mind is that we need to build a decision tree
40+
where `D` means moving down and `R` means moving right. For example in case
41+
of boars `width = 3` and `height = 2` we will have the following decision tree:
42+
43+
```
44+
START
45+
/ \
46+
D R
47+
/ / \
48+
R D R
49+
/ / \
50+
R R D
51+
52+
END END END
53+
```
54+
55+
We can see three unique branches here that is the answer to our problem.
56+
57+
**Time Complexity**: `O(2 ^ n)` - roughly in worst case with square board
58+
of size `n`.
59+
60+
**Auxiliary Space Complexity**: `O(m + n)` - since we need to store current path with
61+
positions.
62+
63+
### Dynamic Programming
64+
65+
Let's treat `BOARD[i][j]` as our sub-problem.
66+
67+
Since we have restriction of moving only to the right
68+
and down we might say that number of unique paths to the current
69+
cell is a sum of numbers of unique paths to the cell above the
70+
current one and to the cell to the left of current one.
71+
72+
```
73+
BOARD[i][j] = BOARD[i - 1][j] + BOARD[i][j - 1]; // since we can only move down or right.
74+
```
75+
76+
Base cases are:
77+
78+
```
79+
BOARD[0][any] = 1; // only one way to reach any top slot.
80+
BOARD[any][0] = 1; // only one way to reach any slot in the leftmost column.
81+
```
82+
83+
For the board `3 x 2` our dynamic programming matrix will look like:
84+
85+
| | 0 | 1 | 1 |
86+
|:---:|:---:|:---:|:---:|
87+
|**0**| 0 | 1 | 1 |
88+
|**1**| 1 | 2 | 3 |
89+
90+
Each cell contains the number of unique paths to it. We need
91+
the bottom right one with number `3`.
92+
93+
**Time Complexity**: `O(m * n)` - since we're going through each cell of the DP matrix.
94+
95+
**Auxiliary Space Complexity**: `O(m * n)` - since we need to have DP matrix.
96+
97+
## References
98+
99+
- [LeetCode](https://leetcode.com/problems/unique-paths/description/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import btUniquePaths from '../btUniquePaths';
2+
3+
describe('btUniquePaths', () => {
4+
it('should find the number of unique paths on board', () => {
5+
expect(btUniquePaths(3, 2)).toBe(3);
6+
expect(btUniquePaths(7, 3)).toBe(28);
7+
expect(btUniquePaths(3, 7)).toBe(28);
8+
expect(btUniquePaths(10, 10)).toBe(48620);
9+
expect(btUniquePaths(100, 1)).toBe(1);
10+
expect(btUniquePaths(1, 100)).toBe(1);
11+
});
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import dpUniquePaths from '../dpUniquePaths';
2+
3+
describe('dpUniquePaths', () => {
4+
it('should find the number of unique paths on board', () => {
5+
expect(dpUniquePaths(3, 2)).toBe(3);
6+
expect(dpUniquePaths(7, 3)).toBe(28);
7+
expect(dpUniquePaths(3, 7)).toBe(28);
8+
expect(dpUniquePaths(10, 10)).toBe(48620);
9+
expect(dpUniquePaths(100, 1)).toBe(1);
10+
expect(dpUniquePaths(1, 100)).toBe(1);
11+
});
12+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* BACKTRACKING approach of solving Unique Paths problem.
3+
*
4+
* @param {number} width - Width of the board.
5+
* @param {number} height - Height of the board.
6+
* @param {number[][]} steps - The steps that have been already made.
7+
* @param {number} uniqueSteps - Total number of unique steps.
8+
* @return {number} - Number of unique paths.
9+
*/
10+
export default function btUniquePaths(width, height, steps = [[0, 0]], uniqueSteps = 0) {
11+
// Fetch current position on board.
12+
const currentPos = steps[steps.length - 1];
13+
14+
// Check if we've reached the end.
15+
if (currentPos[0] === width - 1 && currentPos[1] === height - 1) {
16+
// In case if we've reached the end let's increase total
17+
// number of unique steps.
18+
return uniqueSteps + 1;
19+
}
20+
21+
// Let's calculate how many unique path we will have
22+
// by going right and by going down.
23+
let rightUniqueSteps = 0;
24+
let downUniqueSteps = 0;
25+
26+
// Do right step if possible.
27+
if (currentPos[0] < width - 1) {
28+
steps.push([
29+
currentPos[0] + 1,
30+
currentPos[1],
31+
]);
32+
33+
// Calculate how many unique paths we'll get by moving right.
34+
rightUniqueSteps = btUniquePaths(width, height, steps, uniqueSteps);
35+
36+
// BACKTRACK and try another move.
37+
steps.pop();
38+
}
39+
40+
// Do down step if possible.
41+
if (currentPos[1] < height - 1) {
42+
steps.push([
43+
currentPos[0],
44+
currentPos[1] + 1,
45+
]);
46+
47+
// Calculate how many unique paths we'll get by moving down.
48+
downUniqueSteps = btUniquePaths(width, height, steps, uniqueSteps);
49+
50+
// BACKTRACK and try another move.
51+
steps.pop();
52+
}
53+
54+
// Total amount of unique steps will be equal to total amount of
55+
// unique steps by going right plus total amount of unique steps
56+
// by going down.
57+
return rightUniqueSteps + downUniqueSteps;
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* DYNAMIC PROGRAMMING approach of solving Unique Paths problem.
3+
*
4+
* @param {number} width - Width of the board.
5+
* @param {number} height - Height of the board.
6+
* @return {number} - Number of unique paths.
7+
*/
8+
export default function dpUniquePaths(width, height) {
9+
// Init board.
10+
const board = Array(height).fill(null).map(() => {
11+
return Array(width).fill(0);
12+
});
13+
14+
// Base case.
15+
// There is only one way of getting to board[0][any] and
16+
// there is also only one way of getting to board[any][0].
17+
// This is because we have a restriction of moving right
18+
// and down only.
19+
for (let rowIndex = 0; rowIndex < height; rowIndex += 1) {
20+
for (let columnIndex = 0; columnIndex < width; columnIndex += 1) {
21+
if (rowIndex === 0 || columnIndex === 0) {
22+
board[rowIndex][columnIndex] = 1;
23+
}
24+
}
25+
}
26+
27+
// Now, since we have this restriction of moving only to the right
28+
// and down we might say that number of unique paths to the current
29+
// cell is a sum of numbers of unique paths to the cell above the
30+
// current one and to the cell to the left of current one.
31+
for (let rowIndex = 1; rowIndex < height; rowIndex += 1) {
32+
for (let columnIndex = 1; columnIndex < width; columnIndex += 1) {
33+
const uniquesFromTop = board[rowIndex - 1][columnIndex];
34+
const uniquesFromLeft = board[rowIndex][columnIndex - 1];
35+
board[rowIndex][columnIndex] = uniquesFromTop + uniquesFromLeft;
36+
}
37+
}
38+
39+
return board[height - 1][width - 1];
40+
}

0 commit comments

Comments
 (0)