mirror of
https://github.com/trekhleb/javascript-algorithms.git
synced 2025-07-07 09:54:53 +08:00
Add bottom-up dynamic programming solution to Jump Game.
This commit is contained in:
@ -0,0 +1,17 @@
|
|||||||
|
import dpBottomUpJumpGame from '../dpBottomUpJumpGame';
|
||||||
|
|
||||||
|
describe('dpBottomUpJumpGame', () => {
|
||||||
|
it('should solve Jump Game problem in bottom-up dynamic programming manner', () => {
|
||||||
|
expect(dpBottomUpJumpGame([1, 0])).toBeTruthy();
|
||||||
|
expect(dpBottomUpJumpGame([100, 0])).toBeTruthy();
|
||||||
|
expect(dpBottomUpJumpGame([2, 3, 1, 1, 4])).toBeTruthy();
|
||||||
|
expect(dpBottomUpJumpGame([1, 1, 1, 1, 1])).toBeTruthy();
|
||||||
|
expect(dpBottomUpJumpGame([1, 1, 1, 10, 1])).toBeTruthy();
|
||||||
|
expect(dpBottomUpJumpGame([1, 5, 2, 1, 0, 2, 0])).toBeTruthy();
|
||||||
|
|
||||||
|
expect(dpBottomUpJumpGame([1, 0, 1])).toBeFalsy();
|
||||||
|
expect(dpBottomUpJumpGame([3, 2, 1, 0, 4])).toBeFalsy();
|
||||||
|
expect(dpBottomUpJumpGame([0, 0, 0, 0, 0])).toBeFalsy();
|
||||||
|
expect(dpBottomUpJumpGame([5, 4, 3, 2, 1, 0, 0])).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
* BACKTRACKING approach of solving Jump Game.
|
* BACKTRACKING approach of solving Jump Game.
|
||||||
*
|
*
|
||||||
|
* This is the inefficient solution where we try every single jump
|
||||||
|
* pattern that takes us from the first position to the last.
|
||||||
|
* We start from the first position and jump to every index that
|
||||||
|
* is reachable. We repeat the process until last index is reached.
|
||||||
|
* When stuck, backtrack.
|
||||||
|
*
|
||||||
* @param {number[]} numbers - array of possible jump length.
|
* @param {number[]} numbers - array of possible jump length.
|
||||||
* @param {number} startIndex - index from where we start jumping.
|
* @param {number} startIndex - index from where we start jumping.
|
||||||
* @param {number[]} currentJumps - current jumps path.
|
* @param {number[]} currentJumps - current jumps path.
|
||||||
|
46
src/algorithms/uncategorized/jump-game/dpBottomUpJumpGame.js
Normal file
46
src/algorithms/uncategorized/jump-game/dpBottomUpJumpGame.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* DYNAMIC PROGRAMMING BOTTOM-UP approach of solving Jump Game.
|
||||||
|
*
|
||||||
|
* This comes out as an optimisation of DYNAMIC PROGRAMMING TOP-DOWN approach.
|
||||||
|
*
|
||||||
|
* The observation to make here is that we only ever jump to the right.
|
||||||
|
* This means that if we start from the right of the array, every time we
|
||||||
|
* will query a position to our right, that position has already be
|
||||||
|
* determined as being GOOD or BAD. This means we don't need to recurse
|
||||||
|
* anymore, as we will always hit the memo table.
|
||||||
|
*
|
||||||
|
* We call a position in the array a "good" one if starting at that
|
||||||
|
* position, we can reach the last index. Otherwise, that index
|
||||||
|
* is called a "bad" one.
|
||||||
|
*
|
||||||
|
* @param {number[]} numbers - array of possible jump length.
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export default function dpBottomUpJumpGame(numbers) {
|
||||||
|
// Init cells goodness table.
|
||||||
|
const cellsGoodness = Array(numbers.length).fill(undefined);
|
||||||
|
// Mark the last cell as "good" one since it is where we ultimately want to get.
|
||||||
|
cellsGoodness[cellsGoodness.length - 1] = true;
|
||||||
|
|
||||||
|
// Go throw all cells starting from the one before the last
|
||||||
|
// one (since the last one is "good" already) and fill cellsGoodness table.
|
||||||
|
for (let cellIndex = numbers.length - 2; cellIndex >= 0; cellIndex -= 1) {
|
||||||
|
const maxJumpLength = Math.min(
|
||||||
|
numbers[cellIndex],
|
||||||
|
numbers.length - 1 - cellIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let jumpLength = maxJumpLength; jumpLength > 0; jumpLength -= 1) {
|
||||||
|
const nextIndex = cellIndex + jumpLength;
|
||||||
|
if (cellsGoodness[nextIndex] === true) {
|
||||||
|
cellsGoodness[cellIndex] = true;
|
||||||
|
// Once we detected that current cell is good one we don't need to
|
||||||
|
// do further cells checking.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, if the zero's cell is good one then we can jump from it to the end of the array.
|
||||||
|
return cellsGoodness[0] === true;
|
||||||
|
}
|
@ -2,11 +2,14 @@
|
|||||||
* DYNAMIC PROGRAMMING TOP-DOWN approach of solving Jump Game.
|
* DYNAMIC PROGRAMMING TOP-DOWN approach of solving Jump Game.
|
||||||
*
|
*
|
||||||
* This comes out as an optimisation of BACKTRACKING approach.
|
* 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".
|
* It relies on the observation that once we determine that a certain
|
||||||
|
* index is good / bad, this result will never change. This means that
|
||||||
|
* we can store the result and not need to recompute it every time.
|
||||||
*
|
*
|
||||||
* We call a position in the array a "good" one if starting at that
|
* We call a position in the array a "good" one if starting at that
|
||||||
* position, we can reach the last index.
|
* position, we can reach the last index. Otherwise, that index
|
||||||
|
* is called a "bad" one.
|
||||||
*
|
*
|
||||||
* @param {number[]} numbers - array of possible jump length.
|
* @param {number[]} numbers - array of possible jump length.
|
||||||
* @param {number} startIndex - index from where we start jumping.
|
* @param {number} startIndex - index from where we start jumping.
|
||||||
@ -30,8 +33,7 @@ export default function dpTopDownJumpGame(
|
|||||||
const currentCellsGoodness = [...cellsGoodness];
|
const currentCellsGoodness = [...cellsGoodness];
|
||||||
if (!currentCellsGoodness.length) {
|
if (!currentCellsGoodness.length) {
|
||||||
numbers.forEach(() => currentCellsGoodness.push(undefined));
|
numbers.forEach(() => currentCellsGoodness.push(undefined));
|
||||||
// Mark the last cell as "good" one since it is where
|
// Mark the last cell as "good" one since it is where we ultimately want to get.
|
||||||
// we ultimately want to get.
|
|
||||||
currentCellsGoodness[cellsGoodness.length - 1] = true;
|
currentCellsGoodness[cellsGoodness.length - 1] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* GREEDY approach of solving Jump Game.
|
* GREEDY approach of solving Jump Game.
|
||||||
*
|
*
|
||||||
|
* This comes out as an optimisation of DYNAMIC PROGRAMMING BOTTOM_UP approach.
|
||||||
|
*
|
||||||
|
* Once we have our code in the bottom-up state, we can make one final,
|
||||||
|
* important observation. From a given position, when we try to see if
|
||||||
|
* we can jump to a GOOD position, we only ever use one - the first one.
|
||||||
|
* In other words, the left-most one. If we keep track of this left-most
|
||||||
|
* GOOD position as a separate variable, we can avoid searching for it
|
||||||
|
* in the array. Not only that, but we can stop using the array altogether.
|
||||||
|
*
|
||||||
|
* We call a position in the array a "good" one if starting at that
|
||||||
|
* position, we can reach the last index. Otherwise, that index
|
||||||
|
* is called a "bad" one.
|
||||||
|
*
|
||||||
* @param {number[]} numbers - array of possible jump length.
|
* @param {number[]} numbers - array of possible jump length.
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user