From 57c2a334ce87a465f30dc834b325c77f9ee0cae3 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Tue, 10 Jul 2018 07:37:29 +0300 Subject: [PATCH] Add top-down dynamic programming solution to Jump Game. --- .../__test__/dpTopDownJumpGame.test.js | 17 ++++ .../jump-game/dpTopDownJumpGame.js | 78 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 src/algorithms/uncategorized/jump-game/__test__/dpTopDownJumpGame.test.js create mode 100644 src/algorithms/uncategorized/jump-game/dpTopDownJumpGame.js diff --git a/src/algorithms/uncategorized/jump-game/__test__/dpTopDownJumpGame.test.js b/src/algorithms/uncategorized/jump-game/__test__/dpTopDownJumpGame.test.js new file mode 100644 index 00000000..17920c86 --- /dev/null +++ b/src/algorithms/uncategorized/jump-game/__test__/dpTopDownJumpGame.test.js @@ -0,0 +1,17 @@ +import dpTopDownJumpGame from '../dpTopDownJumpGame'; + +describe('dpTopDownJumpGame', () => { + it('should solve Jump Game problem in top-down dynamic programming manner', () => { + expect(dpTopDownJumpGame([1, 0])).toBeTruthy(); + expect(dpTopDownJumpGame([100, 0])).toBeTruthy(); + expect(dpTopDownJumpGame([2, 3, 1, 1, 4])).toBeTruthy(); + expect(dpTopDownJumpGame([1, 1, 1, 1, 1])).toBeTruthy(); + expect(dpTopDownJumpGame([1, 1, 1, 10, 1])).toBeTruthy(); + expect(dpTopDownJumpGame([1, 5, 2, 1, 0, 2, 0])).toBeTruthy(); + + expect(dpTopDownJumpGame([1, 0, 1])).toBeFalsy(); + expect(dpTopDownJumpGame([3, 2, 1, 0, 4])).toBeFalsy(); + expect(dpTopDownJumpGame([0, 0, 0, 0, 0])).toBeFalsy(); + expect(dpTopDownJumpGame([5, 4, 3, 2, 1, 0, 0])).toBeFalsy(); + }); +}); diff --git a/src/algorithms/uncategorized/jump-game/dpTopDownJumpGame.js b/src/algorithms/uncategorized/jump-game/dpTopDownJumpGame.js new file mode 100644 index 00000000..d44506b1 --- /dev/null +++ b/src/algorithms/uncategorized/jump-game/dpTopDownJumpGame.js @@ -0,0 +1,78 @@ +/** + * DYNAMIC PROGRAMMING TOP-DOWN approach of solving Jump Game. + * + * This comes out as an optimisation of BACKTRACKING approach. + * Optimisation is done by using memo table where we store information + * about each cell whether it is "good" or "bad" or "unknown". + * + * We call a position in the array a "good" one if starting at that + * position, we can reach the last index. + * + * @param {number[]} numbers - array of possible jump length. + * @param {number} startIndex - index from where we start jumping. + * @param {number[]} currentJumps - current jumps path. + * @param {boolean[]} cellsGoodness - holds information about whether cell is "good" or "bad" + * @return {boolean} + */ +export default function dpTopDownJumpGame( + numbers, + startIndex = 0, + currentJumps = [], + cellsGoodness = [], +) { + if (startIndex === numbers.length - 1) { + // We've jumped directly to last cell. This situation is a solution. + return true; + } + + // Init cell goodness table if it is empty. + // This is DYNAMIC PROGRAMMING feature. + const currentCellsGoodness = [...cellsGoodness]; + if (!currentCellsGoodness.length) { + numbers.forEach(() => currentCellsGoodness.push(undefined)); + // Mark the last cell as "good" one since it is where + // we ultimately want to get. + currentCellsGoodness[cellsGoodness.length - 1] = true; + } + + // Check what the longest jump we could make from current position. + // We don't need to jump beyond the array. + const maxJumpLength = Math.min( + numbers[startIndex], // Jump is within array. + numbers.length - 1 - startIndex, // Jump goes beyond array. + ); + + // Let's start jumping from startIndex and see whether any + // jump is successful and has reached the end of the array. + for (let jumpLength = maxJumpLength; jumpLength > 0; jumpLength -= 1) { + // Try next jump. + const nextIndex = startIndex + jumpLength; + + // Jump only into "good" or "unknown" cells. + // This is top-down dynamic programming optimisation of backtracking algorithm. + if (currentCellsGoodness[nextIndex] !== false) { + currentJumps.push(nextIndex); + + const isJumpSuccessful = dpTopDownJumpGame( + numbers, + nextIndex, + currentJumps, + currentCellsGoodness, + ); + + // Check if current jump was successful. + if (isJumpSuccessful) { + return true; + } + + // BACKTRACKING. + // If previous jump wasn't successful then retreat and try the next one. + currentJumps.pop(); + + // Mark current cell as "bad" to avoid its deep visiting later. + currentCellsGoodness[nextIndex] = false; + } + } + + return false; +}