From 9bc28008485104d1881b0327683baa37d63a3ad7 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Wed, 14 Nov 2018 17:45:14 +0200 Subject: [PATCH] Add Recursive Staircase Problem. --- README.md | 3 ++ .../recursive-staircase/README.md | 21 +++++++++ .../__test__/recursiveStaircaseBF.test.js | 18 ++++++++ .../__test__/recursiveStaircaseDP.test.js | 18 ++++++++ .../__test__/recursiveStaircaseIT.test.js | 18 ++++++++ .../__test__/recursiveStaircaseMEM.test.js | 18 ++++++++ .../recursiveStaircaseBF.js | 27 ++++++++++++ .../recursiveStaircaseDP.js | 33 ++++++++++++++ .../recursiveStaircaseIT.js | 31 +++++++++++++ .../recursiveStaircaseMEM.js | 44 +++++++++++++++++++ 10 files changed, 231 insertions(+) create mode 100644 src/algorithms/uncategorized/recursive-staircase/README.md create mode 100644 src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseBF.test.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseDP.test.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseIT.test.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseMEM.test.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseBF.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseDP.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseIT.js create mode 100644 src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseMEM.js diff --git a/README.md b/README.md index 081c8550..77b3b27b 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ a set of rules that precisely define a sequence of operations. * `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, dynamic programming and Pascal's Triangle based examples * `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem (dynamic programming and brute force versions) + * `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top (4 solutions) * `A` [N-Queens Problem](src/algorithms/uncategorized/n-queens) * `A` [Knight's Tour](src/algorithms/uncategorized/knight-tour) @@ -151,6 +152,7 @@ algorithm is an abstraction higher than a computer program. * **Brute Force** - look at all the possibilities and selects the best solution * `B` [Linear Search](src/algorithms/search/linear-search) * `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem + * `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top * `A` [Maximum Subarray](src/algorithms/sets/maximum-subarray) * `A` [Travelling Salesman Problem](src/algorithms/graph/travelling-salesman) - shortest possible route that visits each city and returns to the origin city * `A` [Discrete Fourier Transform](src/algorithms/math/fourier-transform) - decompose a function of time (a signal) into the frequencies that make it up @@ -178,6 +180,7 @@ algorithm is an abstraction higher than a computer program. * `B` [Jump Game](src/algorithms/uncategorized/jump-game) * `B` [Unique Paths](src/algorithms/uncategorized/unique-paths) * `B` [Rain Terraces](src/algorithms/uncategorized/rain-terraces) - trapping rain water problem + * `B` [Recursive Staircase](src/algorithms/uncategorized/recursive-staircase) - count the number of ways to reach to the top * `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) diff --git a/src/algorithms/uncategorized/recursive-staircase/README.md b/src/algorithms/uncategorized/recursive-staircase/README.md new file mode 100644 index 00000000..d7e6f814 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/README.md @@ -0,0 +1,21 @@ +# Recursive Staircase Problem + +## The Problem + +There are `n` stairs, a person standing at the bottom wants to reach the top. The person can climb either `1` or `2` stairs at a time. _Count the number of ways, the person can reach the top._ + +![](https://cdncontribute.geeksforgeeks.org/wp-content/uploads/nth-stair.png) + +## The Solution + +This is an interesting problem because there are several ways of how it may be solved that illustrate different programming paradigms. + +- [Brute Force Recursive Solution](./recursiveStaircaseBF.js) - Time: `O(2^n)`; Space: `O(1)` +- [Recursive Solution With Memoization](./recursiveStaircaseMEM.js) - Time: `O(n)`; Space: `O(n)` +- [Dynamic Programming Solution](./recursiveStaircaseDP.js) - Time: `O(n)`; Space: `O(n)` +- [Iterative Solution](./recursiveStaircaseIT.js) - Time: `O(n)`; Space: `O(1)` + +## References + +- [On YouTube by Gayle Laakmann McDowell](https://www.youtube.com/watch?v=eREiwuvzaUM&list=PLLXdhg_r2hKA7DPDsunoDZ-Z769jWn4R8&index=81&t=0s) +- [GeeksForGeeks](https://www.geeksforgeeks.org/count-ways-reach-nth-stair/) diff --git a/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseBF.test.js b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseBF.test.js new file mode 100644 index 00000000..d1cccf42 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseBF.test.js @@ -0,0 +1,18 @@ +import recursiveStaircaseBF from '../recursiveStaircaseBF'; + +describe('recursiveStaircaseBF', () => { + it('should calculate number of variants using Brute Force solution', () => { + expect(recursiveStaircaseBF(-1)).toBe(0); + expect(recursiveStaircaseBF(0)).toBe(0); + expect(recursiveStaircaseBF(1)).toBe(1); + expect(recursiveStaircaseBF(2)).toBe(2); + expect(recursiveStaircaseBF(3)).toBe(3); + expect(recursiveStaircaseBF(4)).toBe(5); + expect(recursiveStaircaseBF(5)).toBe(8); + expect(recursiveStaircaseBF(6)).toBe(13); + expect(recursiveStaircaseBF(7)).toBe(21); + expect(recursiveStaircaseBF(8)).toBe(34); + expect(recursiveStaircaseBF(9)).toBe(55); + expect(recursiveStaircaseBF(10)).toBe(89); + }); +}); diff --git a/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseDP.test.js b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseDP.test.js new file mode 100644 index 00000000..b8edd831 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseDP.test.js @@ -0,0 +1,18 @@ +import recursiveStaircaseDP from '../recursiveStaircaseDP'; + +describe('recursiveStaircaseDP', () => { + it('should calculate number of variants using Dynamic Programming solution', () => { + expect(recursiveStaircaseDP(-1)).toBe(0); + expect(recursiveStaircaseDP(0)).toBe(0); + expect(recursiveStaircaseDP(1)).toBe(1); + expect(recursiveStaircaseDP(2)).toBe(2); + expect(recursiveStaircaseDP(3)).toBe(3); + expect(recursiveStaircaseDP(4)).toBe(5); + expect(recursiveStaircaseDP(5)).toBe(8); + expect(recursiveStaircaseDP(6)).toBe(13); + expect(recursiveStaircaseDP(7)).toBe(21); + expect(recursiveStaircaseDP(8)).toBe(34); + expect(recursiveStaircaseDP(9)).toBe(55); + expect(recursiveStaircaseDP(10)).toBe(89); + }); +}); diff --git a/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseIT.test.js b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseIT.test.js new file mode 100644 index 00000000..2c0eb732 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseIT.test.js @@ -0,0 +1,18 @@ +import recursiveStaircaseIT from '../recursiveStaircaseIT'; + +describe('recursiveStaircaseIT', () => { + it('should calculate number of variants using Iterative solution', () => { + expect(recursiveStaircaseIT(-1)).toBe(0); + expect(recursiveStaircaseIT(0)).toBe(0); + expect(recursiveStaircaseIT(1)).toBe(1); + expect(recursiveStaircaseIT(2)).toBe(2); + expect(recursiveStaircaseIT(3)).toBe(3); + expect(recursiveStaircaseIT(4)).toBe(5); + expect(recursiveStaircaseIT(5)).toBe(8); + expect(recursiveStaircaseIT(6)).toBe(13); + expect(recursiveStaircaseIT(7)).toBe(21); + expect(recursiveStaircaseIT(8)).toBe(34); + expect(recursiveStaircaseIT(9)).toBe(55); + expect(recursiveStaircaseIT(10)).toBe(89); + }); +}); diff --git a/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseMEM.test.js b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseMEM.test.js new file mode 100644 index 00000000..30998732 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/__test__/recursiveStaircaseMEM.test.js @@ -0,0 +1,18 @@ +import recursiveStaircaseMEM from '../recursiveStaircaseMEM'; + +describe('recursiveStaircaseMEM', () => { + it('should calculate number of variants using Brute Force with Memoization', () => { + expect(recursiveStaircaseMEM(-1)).toBe(0); + expect(recursiveStaircaseMEM(0)).toBe(0); + expect(recursiveStaircaseMEM(1)).toBe(1); + expect(recursiveStaircaseMEM(2)).toBe(2); + expect(recursiveStaircaseMEM(3)).toBe(3); + expect(recursiveStaircaseMEM(4)).toBe(5); + expect(recursiveStaircaseMEM(5)).toBe(8); + expect(recursiveStaircaseMEM(6)).toBe(13); + expect(recursiveStaircaseMEM(7)).toBe(21); + expect(recursiveStaircaseMEM(8)).toBe(34); + expect(recursiveStaircaseMEM(9)).toBe(55); + expect(recursiveStaircaseMEM(10)).toBe(89); + }); +}); diff --git a/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseBF.js b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseBF.js new file mode 100644 index 00000000..108aae27 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseBF.js @@ -0,0 +1,27 @@ +/** + * Recursive Staircase Problem (Brute Force Solution). + * + * @param {number} stairsNum - Number of stairs to climb on. + * @return {number} - Number of ways to climb a staircase. + */ +export default function recursiveStaircaseBF(stairsNum) { + if (stairsNum <= 0) { + // There is no way to go down - you climb the stairs only upwards. + // Also if you're standing on the ground floor that you don't need to do any further steps. + return 0; + } + + if (stairsNum === 1) { + // There is only one way to go to the first step. + return 1; + } + + if (stairsNum === 2) { + // There are two ways to get to the second steps: (1 + 1) or (2). + return 2; + } + + // Sum up how many steps we need to take after doing one step up with the number of + // steps we need to take after doing two steps up. + return recursiveStaircaseBF(stairsNum - 1) + recursiveStaircaseBF(stairsNum - 2); +} diff --git a/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseDP.js b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseDP.js new file mode 100644 index 00000000..f8cffcfa --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseDP.js @@ -0,0 +1,33 @@ +/** + * Recursive Staircase Problem (Dynamic Programming Solution). + * + * @param {number} stairsNum - Number of stairs to climb on. + * @return {number} - Number of ways to climb a staircase. + */ +export default function recursiveStaircaseDP(stairsNum) { + if (stairsNum < 0) { + // There is no way to go down - you climb the stairs only upwards. + return 0; + } + + // Init the steps vector that will hold all possible ways to get to the corresponding step. + const steps = new Array(stairsNum + 1).fill(0); + + // Init the number of ways to get to the 0th, 1st and 2nd steps. + steps[0] = 0; + steps[1] = 1; + steps[2] = 2; + + if (stairsNum <= 2) { + // Return the number of ways to get to the 0th or 1st or 2nd steps. + return steps[stairsNum]; + } + + // Calculate every next step based on two previous ones. + for (let currentStep = 3; currentStep <= stairsNum; currentStep += 1) { + steps[currentStep] = steps[currentStep - 1] + steps[currentStep - 2]; + } + + // Return possible ways to get to the requested step. + return steps[stairsNum]; +} diff --git a/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseIT.js b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseIT.js new file mode 100644 index 00000000..e4645047 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseIT.js @@ -0,0 +1,31 @@ +/** + * Recursive Staircase Problem (Iterative Solution). + * + * @param {number} stairsNum - Number of stairs to climb on. + * @return {number} - Number of ways to climb a staircase. + */ +export default function recursiveStaircaseIT(stairsNum) { + if (stairsNum <= 0) { + // There is no way to go down - you climb the stairs only upwards. + // Also you don't need to do anything to stay on the 0th step. + return 0; + } + + // Init the number of ways to get to the 0th, 1st and 2nd steps. + const steps = [1, 2]; + + if (stairsNum <= 2) { + // Return the number of possible ways of how to get to the 1st or 2nd steps. + return steps[stairsNum - 1]; + } + + // Calculate the number of ways to get to the n'th step based on previous ones. + // Comparing to Dynamic Programming solution we don't store info for all the steps but + // rather for two previous ones only. + for (let currentStep = 3; currentStep <= stairsNum; currentStep += 1) { + [steps[0], steps[1]] = [steps[1], steps[0] + steps[1]]; + } + + // Return possible ways to get to the requested step. + return steps[1]; +} diff --git a/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseMEM.js b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseMEM.js new file mode 100644 index 00000000..dcc9f9a4 --- /dev/null +++ b/src/algorithms/uncategorized/recursive-staircase/recursiveStaircaseMEM.js @@ -0,0 +1,44 @@ +/** + * Recursive Staircase Problem (Recursive Solution With Memoization). + * + * @param {number} totalStairs - Number of stairs to climb on. + * @return {number} - Number of ways to climb a staircase. + */ +export default function recursiveStaircaseMEM(totalStairs) { + // Memo table that will hold all recursively calculated results to avoid calculating them + // over and over again. + const memo = []; + + // Recursive closure. + const getSteps = (stairsNum) => { + if (stairsNum <= 0) { + // There is no way to go down - you climb the stairs only upwards. + // Also if you're standing on the ground floor that you don't need to do any further steps. + return 0; + } + + if (stairsNum === 1) { + // There is only one way to go to the first step. + return 1; + } + + if (stairsNum === 2) { + // There are two ways to get to the second steps: (1 + 1) or (2). + return 2; + } + + // Avoid recursion for the steps that we've calculated recently. + if (memo[stairsNum]) { + return memo[stairsNum]; + } + + // Sum up how many steps we need to take after doing one step up with the number of + // steps we need to take after doing two steps up. + memo[stairsNum] = getSteps(stairsNum - 1) + getSteps(stairsNum - 2); + + return memo[stairsNum]; + }; + + // Return possible ways to get to the requested step. + return getSteps(totalStairs); +}