Skip to content

Commit

Permalink
Add Unique Paths problem with backtracking and DP solutions.
Browse files Browse the repository at this point in the history
  • Loading branch information
trekhleb committed Jul 14, 2018
1 parent 863dbdb commit d8fb657
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 0 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ a set of rules that precisely define a sequence of operations.
* `B` [Tower of Hanoi](src/algorithms/uncategorized/hanoi-tower)
* `B` [Square Matrix Rotation](src/algorithms/uncategorized/square-matrix-rotation) - in-place algorithm
* `B` [Jump Game](src/algorithms/uncategorized/jump-game) - backtracking, dynamic programming (top-down + bottom-up) and greedy examples
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) - backtracking and dynamic programming examples
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)

Expand Down Expand Up @@ -151,6 +152,7 @@ algorithm is an abstraction higher than a computer program.
* **Dynamic Programming** - build up a solution using previously found sub-solutions
* `B` [Fibonacci Number](src/algorithms/math/fibonacci)
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths)
* `A` [Levenshtein Distance](src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences
* `A` [Longest Common Subsequence](src/algorithms/sets/longest-common-subsequence) (LCS)
* `A` [Longest Common Substring](src/algorithms/string/longest-common-substring)
Expand All @@ -166,6 +168,7 @@ algorithm is an abstraction higher than a computer program.
if it satisfies all conditions, and only then continue generating subsequent solutions. Otherwise, backtrack, and go on a
different path of finding a solution. Normally the DFS traversal of state-space is being used.
* `B` [Jump Game](src/algorithms/uncategorized/jump-game)
* `B` [Unique Paths](src/algorithms/uncategorized/unique-paths)
* `A` [Hamiltonian Cycle](src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
* `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens)
* `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour)
Expand Down
99 changes: 99 additions & 0 deletions src/algorithms/uncategorized/unique-paths/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Unique Paths Problem

A robot is located at the top-left corner of a `m x n` grid
(marked 'Start' in the diagram below).

The robot can only move either down or right at any point in
time. The robot is trying to reach the bottom-right corner
of the grid (marked 'Finish' in the diagram below).

How many possible unique paths are there?

![Unique Paths](https://leetcode.com/static/images/problemset/robot_maze.png)

## Examples

**Example #1**

```
Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right
```

**Example #2**

```
Input: m = 7, n = 3
Output: 28
```

## Algorithms

### Backtracking

First thought that might came to mind is that we need to build a decision tree
where `D` means moving down and `R` means moving right. For example in case
of boars `width = 3` and `height = 2` we will have the following decision tree:

```
START
/ \
D R
/ / \
R D R
/ / \
R R D
END END END
```

We can see three unique branches here that is the answer to our problem.

**Time Complexity**: `O(2 ^ n)` - roughly in worst case with square board
of size `n`.

**Auxiliary Space Complexity**: `O(m + n)` - since we need to store current path with
positions.

### Dynamic Programming

Let's treat `BOARD[i][j]` as our sub-problem.

Since we have restriction of moving only to the right
and down we might say that number of unique paths to the current
cell is a sum of numbers of unique paths to the cell above the
current one and to the cell to the left of current one.

```
BOARD[i][j] = BOARD[i - 1][j] + BOARD[i][j - 1]; // since we can only move down or right.
```

Base cases are:

```
BOARD[0][any] = 1; // only one way to reach any top slot.
BOARD[any][0] = 1; // only one way to reach any slot in the leftmost column.
```

For the board `3 x 2` our dynamic programming matrix will look like:

| | 0 | 1 | 1 |
|:---:|:---:|:---:|:---:|
|**0**| 0 | 1 | 1 |
|**1**| 1 | 2 | 3 |

Each cell contains the number of unique paths to it. We need
the bottom right one with number `3`.

**Time Complexity**: `O(m * n)` - since we're going through each cell of the DP matrix.

**Auxiliary Space Complexity**: `O(m * n)` - since we need to have DP matrix.

## References

- [LeetCode](https://leetcode.com/problems/unique-paths/description/)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import btUniquePaths from '../btUniquePaths';

describe('btUniquePaths', () => {
it('should find the number of unique paths on board', () => {
expect(btUniquePaths(3, 2)).toBe(3);
expect(btUniquePaths(7, 3)).toBe(28);
expect(btUniquePaths(3, 7)).toBe(28);
expect(btUniquePaths(10, 10)).toBe(48620);
expect(btUniquePaths(100, 1)).toBe(1);
expect(btUniquePaths(1, 100)).toBe(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import dpUniquePaths from '../dpUniquePaths';

describe('dpUniquePaths', () => {
it('should find the number of unique paths on board', () => {
expect(dpUniquePaths(3, 2)).toBe(3);
expect(dpUniquePaths(7, 3)).toBe(28);
expect(dpUniquePaths(3, 7)).toBe(28);
expect(dpUniquePaths(10, 10)).toBe(48620);
expect(dpUniquePaths(100, 1)).toBe(1);
expect(dpUniquePaths(1, 100)).toBe(1);
});
});
58 changes: 58 additions & 0 deletions src/algorithms/uncategorized/unique-paths/btUniquePaths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* BACKTRACKING approach of solving Unique Paths problem.
*
* @param {number} width - Width of the board.
* @param {number} height - Height of the board.
* @param {number[][]} steps - The steps that have been already made.
* @param {number} uniqueSteps - Total number of unique steps.
* @return {number} - Number of unique paths.
*/
export default function btUniquePaths(width, height, steps = [[0, 0]], uniqueSteps = 0) {
// Fetch current position on board.
const currentPos = steps[steps.length - 1];

// Check if we've reached the end.
if (currentPos[0] === width - 1 && currentPos[1] === height - 1) {
// In case if we've reached the end let's increase total
// number of unique steps.
return uniqueSteps + 1;
}

// Let's calculate how many unique path we will have
// by going right and by going down.
let rightUniqueSteps = 0;
let downUniqueSteps = 0;

// Do right step if possible.
if (currentPos[0] < width - 1) {
steps.push([
currentPos[0] + 1,
currentPos[1],
]);

// Calculate how many unique paths we'll get by moving right.
rightUniqueSteps = btUniquePaths(width, height, steps, uniqueSteps);

// BACKTRACK and try another move.
steps.pop();
}

// Do down step if possible.
if (currentPos[1] < height - 1) {
steps.push([
currentPos[0],
currentPos[1] + 1,
]);

// Calculate how many unique paths we'll get by moving down.
downUniqueSteps = btUniquePaths(width, height, steps, uniqueSteps);

// BACKTRACK and try another move.
steps.pop();
}

// Total amount of unique steps will be equal to total amount of
// unique steps by going right plus total amount of unique steps
// by going down.
return rightUniqueSteps + downUniqueSteps;
}
40 changes: 40 additions & 0 deletions src/algorithms/uncategorized/unique-paths/dpUniquePaths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* DYNAMIC PROGRAMMING approach of solving Unique Paths problem.
*
* @param {number} width - Width of the board.
* @param {number} height - Height of the board.
* @return {number} - Number of unique paths.
*/
export default function dpUniquePaths(width, height) {
// Init board.
const board = Array(height).fill(null).map(() => {
return Array(width).fill(0);
});

// Base case.
// There is only one way of getting to board[0][any] and
// there is also only one way of getting to board[any][0].
// This is because we have a restriction of moving right
// and down only.
for (let rowIndex = 0; rowIndex < height; rowIndex += 1) {
for (let columnIndex = 0; columnIndex < width; columnIndex += 1) {
if (rowIndex === 0 || columnIndex === 0) {
board[rowIndex][columnIndex] = 1;
}
}
}

// Now, since we have this restriction of moving only to the right
// and down we might say that number of unique paths to the current
// cell is a sum of numbers of unique paths to the cell above the
// current one and to the cell to the left of current one.
for (let rowIndex = 1; rowIndex < height; rowIndex += 1) {
for (let columnIndex = 1; columnIndex < width; columnIndex += 1) {
const uniquesFromTop = board[rowIndex - 1][columnIndex];
const uniquesFromLeft = board[rowIndex][columnIndex - 1];
board[rowIndex][columnIndex] = uniquesFromTop + uniquesFromLeft;
}
}

return board[height - 1][width - 1];
}

0 comments on commit d8fb657

Please sign in to comment.