Dylan  Iqbal

Dylan Iqbal

1629357900

JavaScript Algorithms and Data Structures: Everything You Need to Know

This repository contains JavaScript based examples of many popular algorithms and data structures.

Each algorithm and data structure has its own separate README with related explanations and links for further reading (including ones to YouTube videos).

Read this in other languages: 简体中文, 繁體中文, 한국어, 日本語, Polski, Français, Español, Português, Русский, Türk, Italiana, Bahasa Indonesia, Українська, Arabic, Deutsch

☝ Note that this project is meant to be used for learning and researching purposes only, and it is not meant to be used for production.

Data Structures

A data structure is a particular way of organizing and storing data in a computer so that it can be accessed and modified efficiently. More precisely, a data structure is a collection of data values, the relationships among them, and the functions or operations that can be applied to the data.

B - Beginner, A - Advanced

Algorithms

An algorithm is an unambiguous specification of how to solve a class of problems. It is a set of rules that precisely define a sequence of operations.

B - Beginner, A - Advanced

Algorithms by Topic

Algorithms by Paradigm

An algorithmic paradigm is a generic method or approach which underlies the design of a class of algorithms. It is an abstraction higher than the notion of an algorithm, just as an algorithm is an abstraction higher than a computer program.

How to use this repository

Install all dependencies

npm install

Run ESLint

You may want to run it to check code quality.

npm run lint

Run all tests

npm test

Run tests by name

npm test -- 'LinkedList'

Troubleshooting

In case if linting or testing is failing try to delete the node_modules folder and re-install npm packages:

rm -rf ./node_modules
npm i

Playground

You may play with data-structures and algorithms in ./src/playground/playground.js file and write tests for it in ./src/playground/__test__/playground.test.js.

Then just simply run the following command to test if your playground code works as expected:

npm test -- 'playground'

Useful Information

References

▶ Data Structures and Algorithms on YouTube

Big O Notation

Big O notation is used to classify algorithms according to how their running time or space requirements grow as the input size grows. On the chart below you may find most common orders of growth of algorithms specified in Big O notation.

Big O graphs

Source: Big O Cheat Sheet.

Below is the list of some of the most used Big O notations and their performance comparisons against different sizes of the input data.

Big O NotationComputations for 10 elementsComputations for 100 elementsComputations for 1000 elements
O(1)111
O(log N)369
O(N)101001000
O(N log N)306009000
O(N^2)100100001000000
O(2^N)10241.26e+291.07e+301
O(N!)36288009.3e+1574.02e+2567

Data Structure Operations Complexity

Data StructureAccessSearchInsertionDeletionComments
Array1nnn 
Stacknn11 
Queuenn11 
Linked Listnn1n 
Hash Table-nnnIn case of perfect hash function costs would be O(1)
Binary Search TreennnnIn case of balanced tree costs would be O(log(n))
B-Treelog(n)log(n)log(n)log(n) 
Red-Black Treelog(n)log(n)log(n)log(n) 
AVL Treelog(n)log(n)log(n)log(n) 
Bloom Filter-11-False positives are possible while searching

Array Sorting Algorithms Complexity

NameBestAverageWorstMemoryStableComments
Bubble sortnn2n21Yes 
Insertion sortnn2n21Yes 
Selection sortn2n2n21No 
Heap sortn log(n)n log(n)n log(n)1No 
Merge sortn log(n)n log(n)n log(n)nYes 
Quick sortn log(n)n log(n)n2log(n)NoQuicksort is usually done in-place with O(log(n)) stack space
Shell sortn log(n)depends on gap sequencen (log(n))21No 
Counting sortn + rn + rn + rn + rYesr - biggest number in array
Radix sortn * kn * kn * kn + kYesk - length of longest key

Download Details:

Author: trekhleb
Official Website: https://github.com/trekhleb/javascript-algorithms
License: MIT

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Everything You Need to Know
Dylan  Iqbal

Dylan Iqbal

1629162660

JavaScript Algorithms and Data Structures: Brute Force - Discrete Four

Definitions

The Fourier Transform (FT) decomposes a function of time (a signal) into the frequencies that make it up, in a way similar to how a musical chord can be expressed as the frequencies (or pitches) of its constituent notes.

The Discrete Fourier Transform (DFT) converts a finite sequence of equally-spaced samples of a function into a same-length sequence of equally-spaced samples of the discrete-time Fourier transform (DTFT), which is a complex-valued function of frequency. The interval at which the DTFT is sampled is the reciprocal of the duration of the input sequence. An inverse DFT is a Fourier series, using the DTFT samples as coefficients of complex sinusoids at the corresponding DTFT frequencies. It has the same sample-values as the original input sequence. The DFT is therefore said to be a frequency domain representation of the original input sequence. If the original sequence spans all the non-zero values of a function, its DTFT is continuous (and periodic), and the DFT provides discrete samples of one cycle. If the original sequence is one cycle of a periodic function, the DFT provides all the non-zero values of one DTFT cycle.

The Discrete Fourier transform transforms a sequence of N complex numbers:

{xn} = x0, x1, x2 ..., xN-1

into another sequence of complex numbers:

{Xk} = X0, X1, X2 ..., XN-1

which is defined by:

DFT

The Discrete-Time Fourier Transform (DTFT) is a form of Fourier analysis that is applicable to the uniformly-spaced samples of a continuous function. The term discrete-time refers to the fact that the transform operates on discrete data (samples) whose interval often has units of time. From only the samples, it produces a function of frequency that is a periodic summation of the continuous Fourier transform of the original continuous function.

A Fast Fourier Transform (FFT) is an algorithm that samples a signal over a period of time (or space) and divides it into its frequency components. These components are single sinusoidal oscillations at distinct frequencies each with their own amplitude and phase.

This transformation is illustrated in Diagram below. Over the time period measured in the diagram, the signal contains 3 distinct dominant frequencies.

View of a signal in the time and frequency domain:

FFT

An FFT algorithm computes the discrete Fourier transform (DFT) of a sequence, or its inverse (IFFT). Fourier analysis converts a signal from its original domain to a representation in the frequency domain and vice versa. An FFT rapidly computes such transformations by factorizing the DFT matrix into a product of sparse (mostly zero) factors. As a result, it manages to reduce the complexity of computing the DFT from O(n2), which arises if one simply applies the definition of DFT, to O(n log n), where n is the data size.

Here a discrete Fourier analysis of a sum of cosine waves at 10, 20, 30, 40, and 50 Hz:

FFT

Explanation

The Fourier Transform is one of deepest insights ever made. Unfortunately, the meaning is buried within dense equations:

and

Rather than jumping into the symbols, let's experience the key idea firsthand. Here's a plain-English metaphor:

  • What does the Fourier Transform do? Given a smoothie, it finds the recipe.
  • How? Run the smoothie through filters to extract each ingredient.
  • Why? Recipes are easier to analyze, compare, and modify than the smoothie itself.
  • How do we get the smoothie back? Blend the ingredients.

Think With Circles, Not Just Sinusoids

The Fourier Transform is about circular paths (not 1-d sinusoids) and Euler's formula is a clever way to generate one:

Must we use imaginary exponents to move in a circle? Nope. But it's convenient and compact. And sure, we can describe our path as coordinated motion in two dimensions (real and imaginary), but don't forget the big picture: we're just moving in a circle.

Discovering The Full Transform

The big insight: our signal is just a bunch of time spikes! If we merge the recipes for each time spike, we should get the recipe for the full signal.

The Fourier Transform builds the recipe frequency-by-frequency:

A few notes:

  • N = number of time samples we have
  • n = current sample we're considering (0 ... N-1)
  • xn = value of the signal at time n
  • k = current frequency we're considering (0 Hertz up to N-1 Hertz)
  • Xk = amount of frequency k in the signal (amplitude and phase, a complex number)
  • The 1/N factor is usually moved to the reverse transform (going from frequencies back to time). This is allowed, though I prefer 1/N in the forward transform since it gives the actual sizes for the time spikes. You can get wild and even use 1/sqrt(N) on both transforms (going forward and back creates the 1/N factor).
  • n/N is the percent of the time we've gone through. 2 _ pi _ k is our speed in radians / sec. e^-ix is our backwards-moving circular path. The combination is how far we've moved, for this speed and time.
  • The raw equations for the Fourier Transform just say "add the complex numbers". Many programming languages cannot handle complex numbers directly, so you convert everything to rectangular coordinates and add those.

Stuart Riffle has a great interpretation of the Fourier Transform:

References

Read this in other languages: français.

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Brute Force - Discrete Four
Dylan  Iqbal

Dylan Iqbal

1629158820

JavaScript Algorithms and Data Structures: Brute Force - Travelling Salesman Problem

The travelling salesman problem (TSP) asks the following question: "Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city and returns to the origin city?"

Travelling Salesman

Solution of a travelling salesman problem: the black line shows the shortest possible loop that connects every red dot.

Travelling Salesman Graph

TSP can be modelled as an undirected weighted graph, such that cities are the graph's vertices, paths are the graph's edges, and a path's distance is the edge's weight. It is a minimization problem starting and finishing at a specified vertex after having visited each other vertex exactly once. Often, the model is a complete graph (i.e. each pair of vertices is connected by an edge). If no path exists between two cities, adding an arbitrarily long edge will complete the graph without affecting the optimal tour.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Brute Force - Travelling Salesman Problem
Dylan  Iqbal

Dylan Iqbal

1629154800

JavaScript Algorithms and Data Structures: Brute Force - Maximum Subarray

The maximum subarray problem is the task of finding the contiguous subarray within a one-dimensional array, a[1...n], of numbers which has the largest sum, where,

Maximum subarray

Maximum subarray

Example

The list usually contains both positive and negative numbers along with 0. For example, for the array of values −2, 1, −3, 4, −1, 2, 1, −5, 4 the contiguous subarray with the largest sum is 4, −1, 2, 1, with sum 6.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Brute Force - Maximum Subarray
Dylan  Iqbal

Dylan Iqbal

1629151020

JavaScript Algorithms and Data Structures: Brute Force - Recursive Staircase

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.

The Solution

This is an interesting problem because there are several ways of how it may be solved that illustrate different programming paradigms.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Brute Force - Recursive Staircase
Dylan  Iqbal

Dylan Iqbal

1629147300

JavaScript Algorithms and Data Structures: Brute Force - Rain Terraces

Given an array of non-negative integers representing terraces in an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

Rain Terraces

Examples

Example #1

Input: arr[] = [2, 0, 2]
Output: 2
Structure is like below:

| |
|_|

We can trap 2 units of water in the middle gap.

Example #2

Input: arr[] = [3, 0, 0, 2, 0, 4]
Output: 10
Structure is like below:

     |
|    |
|  | |
|__|_| 

We can trap "3*2 units" of water between 3 an 2,
"1 unit" on top of bar 2 and "3 units" between 2 
and 4. See below diagram also.

Example #3

Input: arr[] = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
Output: 6
Structure is like below:

       | 
   |   || |
_|_||_||||||

Trap "1 unit" between first 1 and 2, "4 units" between
first 2 and 3 and "1 unit" between second last 1 and last 2.

The Algorithm

An element of array can store water if there are higher bars on left and right. We can find amount of water to be stored in every element by finding the heights of bars on left and right sides. The idea is to compute amount of water that can be stored in every element of array. For example, consider the array [3, 0, 0, 2, 0, 4], We can trap "3*2 units" of water between 3 an 2, "1 unit" on top of bar 2 and "3 units" between 2 and 4. See below diagram also.

Approach 1: Brute force

Intuition

For each element in the array, we find the maximum level of water it can trap after the rain, which is equal to the minimum of maximum height of bars on both the sides minus its own height.

Steps

  • Initialize answer = 0
  • Iterate the array from left to right:
    • Initialize max_left = 0 and max_right = 0
    • Iterate from the current element to the beginning of array updating: max_left = max(max_left, height[j])
    • Iterate from the current element to the end of array updating: max_right = max(max_right, height[j])
    • Add min(max_left, max_right) − height[i] to answer

Complexity Analysis

Time complexity: O(n^2). For each element of array, we iterate the left and right parts.

Auxiliary space complexity: O(1) extra space.

Approach 2: Dynamic Programming

Intuition

In brute force, we iterate over the left and right parts again and again just to find the highest bar size up to that index. But, this could be stored. Voila, dynamic programming.

So we may pre-compute highest bar on left and right of every bar in O(n) time. Then use these pre-computed values to find the amount of water in every array element.

The concept is illustrated as shown:

DP Trapping Rain Water

Steps

  • Find maximum height of bar from the left end up to an index i in the array left_max.
  • Find maximum height of bar from the right end up to an index i in the array right_max.
  • Iterate over the height array and update answer:
    • Add min(max_left[i], max_right[i]) − height[i] to answer.

Complexity Analysis

Time complexity: O(n). We store the maximum heights upto a point using 2 iterations of O(n) each. We finally update answer using the stored values in O(n).

Auxiliary space complexity: O(n) extra space. Additional space for left_max and right_max arrays than in Approach 1.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures 

JavaScript Algorithms and Data Structures: Brute Force - Rain Terraces
Dylan  Iqbal

Dylan Iqbal

1629143580

JavaScript Algorithms and Data Structures: Brute Force - Linear Search

In computer science, linear search or sequential search is a method for finding a target value within a list. It sequentially checks each element of the list for the target value until a match is found or until all the elements have been searched. Linear search runs in at worst linear time and makes at most n comparisons, where n is the length of the list.

Linear Search

Complexity

Time Complexity: O(n) - since in worst case we're checking each element exactly once.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Brute Force - Linear Search
Dylan  Iqbal

Dylan Iqbal

1629139740

JavaScript Algorithms and Data Structures: Knight's Tour

A knight's tour is a sequence of moves of a knight on a chessboard such that the knight visits every square only once. If the knight ends on a square that is one knight's move from the beginning square (so that it could tour the board again immediately, following the same path), the tour is closed, otherwise it is open.

The knight's tour problem is the mathematical problem of finding a knight's tour. Creating a program to find a knight's tour is a common problem given to computer science students. Variations of the knight's tour problem involve chessboards of different sizes than the usual 8×8, as well as irregular (non-rectangular) boards.

The knight's tour problem is an instance of the more general Hamiltonian path problem in graph theory. The problem of finding a closed knight's tour is similarly an instance of the Hamiltonian cycle problem.

Knight's Tour

An open knight's tour of a chessboard.

Knight's Tour

An animation of an open knight's tour on a 5 by 5 board.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Knight's Tour
Dylan  Iqbal

Dylan Iqbal

1629135960

JavaScript Algorithms and Data Structures: N-Queens Problem

The eight queens puzzle is the problem of placing eight chess queens on an 8×8 chessboard so that no two queens threaten each other. Thus, a solution requires that no two queens share the same row, column, or diagonal. The eight queens puzzle is an example of the more general n queens problem of placing n non-attacking queens on an n×n chessboard, for which solutions exist for all natural numbers n with the exception of n=2 and n=3.

For example, following is a solution for 4 Queen problem.

N Queens

The expected output is a binary matrix which has 1s for the blocks where queens are placed. For example following is the output matrix for above 4 queen solution.

{ 0,  1,  0,  0}
{ 0,  0,  0,  1}
{ 1,  0,  0,  0}
{ 0,  0,  1,  0}

Naive Algorithm

Generate all possible configurations of queens on board and print a configuration that satisfies the given constraints.

while there are untried configurations
{
   generate the next configuration
   if queens don't attack in this configuration then
   {
      print this configuration;
   }
}

Backtracking Algorithm

The idea is to place queens one by one in different columns, starting from the leftmost column. When we place a queen in a column, we check for clashes with already placed queens. In the current column, if we find a row for which there is no clash, we mark this row and column as part of the solution. If we do not find such a row due to clashes then we backtrack and return false.

1) Start in the leftmost column
2) If all queens are placed
    return true
3) Try all rows in the current column.  Do following for every tried row.
    a) If the queen can be placed safely in this row then mark this [row, 
        column] as part of the solution and recursively check if placing  
        queen here leads to a solution.
    b) If placing queen in [row, column] leads to a solution then return 
        true.
    c) If placing queen doesn't lead to a solution then umark this [row, 
        column] (Backtrack) and go to step (a) to try other rows.
3) If all rows have been tried and nothing worked, return false to trigger 
    backtracking.

Bitwise Solution

Bitwise algorithm basically approaches the problem like this:

  • Queens can attack diagonally, vertically, or horizontally. As a result, there can only be one queen in each row, one in each column, and at most one on each diagonal.
  • Since we know there can only one queen per row, we will start at the first row, place a queen, then move to the second row, place a second queen, and so on until either a) we reach a valid solution or b) we reach a dead end (ie. we can't place a queen such that it is "safe" from the other queens).
  • Since we are only placing one queen per row, we don't need to worry about horizontal attacks, since no queen will ever be on the same row as another queen.
  • That means we only need to check three things before placing a queen on a certain square: 1) The square's column doesn't have any other queens on it, 2) the square's left diagonal doesn't have any other queens on it, and 3) the square's right diagonal doesn't have any other queens on it.
  • If we ever reach a point where there is nowhere safe to place a queen, we can give up on our current attempt and immediately test out the next possibility.

First let's talk about the recursive function. You'll notice that it accepts 3 parameters: leftDiagonal, column, and rightDiagonal. Each of these is technically an integer, but the algorithm takes advantage of the fact that an integer is represented by a sequence of bits. So, think of each of these parameters as a sequence of N bits.

Each bit in each of the parameters represents whether the corresponding location on the current row is "available".

For example:

  • For N=4, column having a value of 0010 would mean that the 3rd column is already occupied by a queen.
  • For N=8, ld having a value of 00011000 at row 5 would mean that the top-left-to-bottom-right diagonals that pass through columns 4 and 5 of that row are already occupied by queens.

Below is a visual aid for leftDiagonal, column, and rightDiagonal.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: N-Queens Problem
Dylan  Iqbal

Dylan Iqbal

1629132240

JavaScript Algorithms and Data Structures: Best Time To Buy Sell Stock

Task Description

Say you have an array prices for which the i-th element is the price of a given stock on day i.

Find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example #1

Input: [7, 1, 5, 3, 6, 4]
Output: 7

Explanation: Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3.

Example #2

Input: [1, 2, 3, 4, 5]
Output: 4

Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again.

Example #3

Input: [7, 6, 4, 3, 1]
Output: 0

Explanation: In this case, no transaction is done, i.e. max profit = 0.

Possible Solutions

Divide and conquer approach O(2^n)

We may try all combinations of buying and selling and find out the most profitable one by applying divide and conquer approach.

Let's say we have an array of prices [7, 6, 4, 3, 1] and we're on the 1st day of trading (at the very beginning of the array). At this point we may say that the overall maximum profit would be the maximum of two following values:

  1. Option 1: Keep the money → profit would equal to the profit from buying/selling the rest of the stocks → keepProfit = profit([6, 4, 3, 1]).
  2. Option 2: Buy/sell at current price → profit in this case would equal to the profit from buying/selling the rest of the stocks plus (or minus, depending on whether we're selling or buying) the current stock price → buySellProfit = -7 + profit([6, 4, 3, 1]).

The overall profit would be equal to → overalProfit = Max(keepProfit, buySellProfit).

As you can see the profit([6, 4, 3, 1]) task is being solved in the same recursive manner.

See the full code example in dqBestTimeToBuySellStocks.js

Time Complexity

As you may see, every recursive call will produce 2 more recursive branches. The depth of the recursion will be n (size of prices array) and thus, the time complexity will equal to O(2^n).

As you may see, this is very inefficient. For example for just 20 prices the number of recursive calls will be somewhere close to 2M!

Additional Space Complexity

If we avoid cloning the prices array between recursive function calls and will use the array pointer then additional space complexity will be proportional to the depth of the recursion: O(n)

Peak Valley Approach O(n)

If we plot the prices array (i.e. [7, 1, 5, 3, 6, 4]) we may notice that the points of interest are the consecutive valleys and peaks

Peak Valley Approach

Image source: LeetCode

So, if we will track the growing price and will sell the stocks immediately before the price goes down we'll get the maximum profit (remember, we bought the stock in the valley at its low price).

See the full code example in peakvalleyBestTimeToBuySellStocks.js

Time Complexity

Since the algorithm requires only one pass through the prices array, the time complexity would equal O(n).

Additional Space Complexity

Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is O(1).

Accumulator Approach O(n)

There is even simpler approach exists. Let's say we have the prices array which looks like this [1, 7, 2, 3, 6, 7, 6, 7]:

Simple One Pass

Image source: LeetCode

You may notice, that we don't even need to keep tracking of a constantly growing price. Instead, we may simply add the price difference for all growing segments of the chart which eventually sums up to the highest possible profit,

See the full code example in accumulatorBestTimeToBuySellStocks.js

Time Complexity

Since the algorithm requires only one pass through the prices array, the time complexity would equal O(n).

Additional Space Complexity

Except of the prices array itself the algorithm consumes the constant amount of memory. Thus, additional space complexity is O(1).

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Best Time To Buy Sell Stock
Dylan  Iqbal

Dylan Iqbal

1629128520

JavaScript Algorithms and Data Structures: Recursive Staircase

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.

The Solution

This is an interesting problem because there are several ways of how it may be solved that illustrate different programming paradigms.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Recursive Staircase
Dylan  Iqbal

Dylan Iqbal

1629124860

JavaScript Algorithms and Data Structures: Rain Terraces

Given an array of non-negative integers representing terraces in an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.

Rain Terraces

Examples

Example #1

Input: arr[] = [2, 0, 2]
Output: 2
Structure is like below:

| |
|_|

We can trap 2 units of water in the middle gap.

Example #2

Input: arr[] = [3, 0, 0, 2, 0, 4]
Output: 10
Structure is like below:

     |
|    |
|  | |
|__|_| 

We can trap "3*2 units" of water between 3 an 2,
"1 unit" on top of bar 2 and "3 units" between 2 
and 4. See below diagram also.

Example #3

Input: arr[] = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
Output: 6
Structure is like below:

       | 
   |   || |
_|_||_||||||

Trap "1 unit" between first 1 and 2, "4 units" between
first 2 and 3 and "1 unit" between second last 1 and last 2.

The Algorithm

An element of array can store water if there are higher bars on left and right. We can find amount of water to be stored in every element by finding the heights of bars on left and right sides. The idea is to compute amount of water that can be stored in every element of array. For example, consider the array [3, 0, 0, 2, 0, 4], We can trap "3*2 units" of water between 3 an 2, "1 unit" on top of bar 2 and "3 units" between 2 and 4. See below diagram also.

Approach 1: Brute force

Intuition

For each element in the array, we find the maximum level of water it can trap after the rain, which is equal to the minimum of maximum height of bars on both the sides minus its own height.

Steps

  • Initialize answer = 0
  • Iterate the array from left to right:
    • Initialize max_left = 0 and max_right = 0
    • Iterate from the current element to the beginning of array updating: max_left = max(max_left, height[j])
    • Iterate from the current element to the end of array updating: max_right = max(max_right, height[j])
    • Add min(max_left, max_right) − height[i] to answer

Complexity Analysis

Time complexity: O(n^2). For each element of array, we iterate the left and right parts.

Auxiliary space complexity: O(1) extra space.

Approach 2: Dynamic Programming

Intuition

In brute force, we iterate over the left and right parts again and again just to find the highest bar size up to that index. But, this could be stored. Voila, dynamic programming.

So we may pre-compute highest bar on left and right of every bar in O(n) time. Then use these pre-computed values to find the amount of water in every array element.

The concept is illustrated as shown:

DP Trapping Rain Water

Steps

  • Find maximum height of bar from the left end up to an index i in the array left_max.
  • Find maximum height of bar from the right end up to an index i in the array right_max.
  • Iterate over the height array and update answer:
    • Add min(max_left[i], max_right[i]) − height[i] to answer.

Complexity Analysis

Time complexity: O(n). We store the maximum heights upto a point using 2 iterations of O(n) each. We finally update answer using the stored values in O(n).

Auxiliary space complexity: O(n) extra space. Additional space for left_max and right_max arrays than in Approach 1.

References

 

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Rain Terraces
Dylan  Iqbal

Dylan Iqbal

1629121080

JavaScript Algorithms and Data Structures: Unique Paths

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

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:

 011
0011
1123

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.

Pascal's Triangle Based

This question is actually another form of Pascal Triangle.

The corner of this rectangle is at m + n - 2 line, and at min(m, n) - 1 position of the Pascal's Triangle.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Unique Paths
Dylan  Iqbal

Dylan Iqbal

1629117360

JavaScript Algorithms and Data Structures: Jump Game

The Problem

Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position.

Determine if you are able to reach the last index.

Example #1

Input: [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example #2

Input: [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum
             jump length is 0, which makes it impossible to reach the last index.

Naming

We call a position in the array a "good index" if starting at that position, we can reach the last index. Otherwise, that index is called a "bad index". The problem then reduces to whether or not index 0 is a "good index".

Solutions

Approach 1: Backtracking

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.

See backtrackingJumpGame.js file

Time complexity:: O(2^n). There are 2n (upper bound) ways of jumping from the first position to the last, where n is the length of array nums.

Auxiliary Space Complexity: O(n). Recursion requires additional memory for the stack frames.

Approach 2: Dynamic Programming Top-down

Top-down Dynamic Programming can be thought of as optimized backtracking. 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.

Therefore, for each position in the array, we remember whether the index is good or bad. Let's call this array memo and let its values be either one of: GOOD, BAD, UNKNOWN. This technique is called memoization.

See dpTopDownJumpGame.js file

Time complexity:: O(n^2). For every element in the array, say i, we are looking at the next nums[i] elements to its right aiming to find a GOOD index. nums[i] can be at most n, where n is the length of array nums.

Auxiliary Space Complexity: O(2 * n) = O(n). First n originates from recursion. Second n comes from the usage of the memo table.

Approach 3: Dynamic Programming Bottom-up

Top-down to bottom-up conversion is done by eliminating recursion. In practice, this achieves better performance as we no longer have the method stack overhead and might even benefit from some caching. More importantly, this step opens up possibilities for future optimization. The recursion is usually eliminated by trying to reverse the order of the steps from the 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.

See dpBottomUpJumpGame.js file

Time complexity:: O(n^2). For every element in the array, say i, we are looking at the next nums[i] elements to its right aiming to find a GOOD index. nums[i] can be at most n, where n is the length of array nums.

Auxiliary Space Complexity: O(n). This comes from the usage of the memo table.

Approach 4: Greedy

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.

See greedyJumpGame.js file

Time complexity:: O(n). We are doing a single pass through the nums array, hence n steps, where n is the length of array nums.

Auxiliary Space Complexity: O(1). We are not using any extra memory.

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Jump Game
Dylan  Iqbal

Dylan Iqbal

1629113640

JavaScript Algorithms and Data Structures: Square Matrix Rotation

The Problem

You are given an n x n 2D matrix (representing an image). Rotate the matrix by 90 degrees (clockwise).

Note

You have to rotate the image in-place, which means you have to modify the input 2D matrix directly. DO NOT allocate another 2D matrix and do the rotation.

Examples

Example #1

Given input matrix:

[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
]

Rotate the input matrix in-place such that it becomes:

[
  [7, 4, 1],
  [8, 5, 2],
  [9, 6, 3],
]

Example #2

Given input matrix:

[
  [5, 1, 9, 11],
  [2, 4, 8, 10],
  [13, 3, 6, 7],
  [15, 14, 12, 16],
]

Rotate the input matrix in-place such that it becomes:

[
  [15, 13, 2, 5],
  [14, 3, 4, 1],
  [12, 6, 8, 9],
  [16, 7, 10, 11],
]

Algorithm

We would need to do two reflections of the matrix:

  • reflect vertically
  • reflect diagonally from bottom-left to top-right

Or we also could Furthermore, you can reflect diagonally top-left/bottom-right and reflect horizontally.

A common question is how do you even figure out what kind of reflections to do? Simply rip a square piece of paper, write a random word on it so you know its rotation. Then, flip the square piece of paper around until you figure out how to come to the solution.

Here is an example of how first line may be rotated using diagonal top-right/bottom-left rotation along with horizontal rotation.

Let's say we have a string at the top of the matrix:

A B C
• • •
• • •

Let's do top-right/bottom-left diagonal reflection:

A B C
/ / •
/ • •  

And now let's do horizontal reflection:

A → →
B → →
C → →

The string has been rotated to 90 degree:

• • A
• • B
• • C

References

The Original Article can be found on https://github.com

#javascript #algorithms #datastructures

JavaScript Algorithms and Data Structures: Square Matrix Rotation