JavaScript Coding Test Course, Segment Tree

Problem Description

Problem: Calculate the Sum of a Given Range

An array arr containing n integers is given,
and you need to write a function to handle the following queries:

  • 1. update(index, value) : Updates the indexth value in the array arr to value.
  • 2. rangeSum(left, right) : Calculates the sum from the leftth to the rightth (0-indexing) in the array arr.

Use the given array and queries to efficiently handle the requirements of update and rangeSum.
The size of the array is up to 10^5 and the number of queries is also up to 10^5.

Solution Method

This problem requires efficiently calculating range sums and processing updates, so we can use a Segment Tree.
A Segment Tree is a binary tree-based data structure that stores the given array in intervals (for range sum queries).

Definition of Segment Tree

A Segment Tree has the following properties:

  • Each node stores information about one array interval. This information can be set as the sum, minimum, maximum, etc. of the interval.
  • The height of the tree is O(log n), meaning that both query and update operations take O(log n) time.

Steps to Implement a Segment Tree

To implement a Segment Tree, follow these steps:

  1. Initialization: Initialize the Segment Tree based on the given array.
  2. Range Sum Query: Recursively retrieve the nodes necessary to calculate the sum for a specific interval.
  3. Update: Update the value at a specific index and refresh the relevant segment nodes.

JavaScript Code Implementation


class SegmentTree {
    constructor(arr) {
        this.n = arr.length;
        this.tree = new Array(this.n * 4);
        this.build(arr, 0, 0, this.n - 1);
    }

    build(arr, node, start, end) {
        if (start === end) {
            // Store integer value at leaf node
            this.tree[node] = arr[start];
        } else {
            const mid = Math.floor((start + end) / 2);
            // Define left child
            this.build(arr, node * 2 + 1, start, mid);
            // Define right child
            this.build(arr, node * 2 + 2, mid + 1, end);
            // Define parent node as the sum of both children
            this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
        }
    }

    rangeSum(left, right) {
        return this.sum(0, 0, this.n - 1, left, right);
    }

    sum(node, start, end, left, right) {
        if (right < start || end < left) {
            // Return 0 if requested range does not overlap
            return 0;
        }
        if (left <= start && end <= right) {
            // Return node if requested range is fully included
            return this.tree[node];
        }
        const mid = Math.floor((start + end) / 2);
        const leftSum = this.sum(node * 2 + 1, start, mid, left, right);
        const rightSum = this.sum(node * 2 + 2, mid + 1, end, left, right);
        return leftSum + rightSum;
    }

    update(index, value) {
        this.updateValue(0, 0, this.n - 1, index, value);
    }

    updateValue(node, start, end, index, value) {
        if (start === end) {
            // Update leaf node
            this.tree[node] = value;
        } else {
            const mid = Math.floor((start + end) / 2);
            if (index <= mid) {
                this.updateValue(node * 2 + 1, start, mid, index, value);
            } else {
                this.updateValue(node * 2 + 2, mid + 1, end, index, value);
            }
            // Update parent node
            this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
        }
    }
}

// Example usage
const arr = [1, 3, 5, 7, 9, 11];
const segmentTree = new SegmentTree(arr);
console.log(segmentTree.rangeSum(1, 3)); // 15
segmentTree.update(1, 10);
console.log(segmentTree.rangeSum(1, 3)); // 22

Conclusion

The Segment Tree is a powerful tool for efficiently handling the range sum of arrays.
This data structure allows for updates and range sum calculations with a time complexity of O(log n).
When faced with complex problems in practice, using a Segment Tree can provide many advantages.

Additional Practice Problems

Try practicing the following problems:

  • Use a Segment Tree to find the minimum value in a given array
  • Add a query to add a specific value over an interval
  • Find the maximum value using a Segment Tree

JavaScript Coding Test Course, Finding the Fastest Bus Route

Problem Introduction

You need to write a program that finds the fastest bus route from point A to point B. There are multiple bus routes, and each route passes through specified stops, with different travel times between stops. The ultimate goal is to find the fastest path from a specific point A to point B and return the travel time for that path.

Problem Description

The given routes are expressed as follows. Each route has travel times between stops, and the stops are represented in the following format:

        [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ]
    

Input

The first parameter is an array of bus routes, and the second parameter is point A and point B.

Output

Print the travel time of the fastest route. If there is no route, print -1.

Example

        Input: 
        const routes = [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ];
        const start = "A";
        const end = "B";

        Output: 
        5
    

Solution Method

To solve this problem, we can use a graph traversal algorithm. The stops represent nodes, and the travel times between stops represent the weights of the edges between those nodes. Suitable algorithms include BFS (Breadth-First Search) or Dijkstra’s algorithm. In this problem, Dijkstra’s algorithm is more effective because the weights of all edges may differ, necessitating an optimized method to find the shortest path.

Dijkstra’s Algorithm Overview

Dijkstra’s algorithm is used to find the shortest path in a weighted graph, continuously updating the cost of moving from the current node to adjacent nodes while exploring the path to the target node. For this, we use a priority queue to record each stop and its cost.

Code Implementation

        function getFastestBusRoute(routes, start, end) {
            // Priority queue for processing the stops
            const pq = new MinPriorityQueue();
            const distances = {};
            const parents = {};

            // Initialize distances and priority queue
            for (const route of routes) {
                for (const stop of route.stops) {
                    distances[stop.stop] = Infinity;
                    parents[stop.stop] = null;
                }
            }

            // Starting point
            distances[start] = 0;
            pq.enqueue(start, 0);

            while (!pq.isEmpty()) {
                const currentStop = pq.dequeue().element;

                // If we reach the target stop, return the distance
                if (currentStop === end) {
                    return distances[currentStop];
                }

                for (const route of routes) {
                    for (let i = 0; i < route.stops.length - 1; i++) {
                        const stop1 = route.stops[i].stop;
                        const stop2 = route.stops[i + 1].stop;
                        const time = route.stops[i + 1].time - route.stops[i].time;

                        if (stop1 === currentStop) {
                            const newTime = distances[stop1] + time;
                            if (newTime < distances[stop2]) {
                                distances[stop2] = newTime;
                                parents[stop2] = stop1;
                                pq.enqueue(stop2, newTime);
                            }
                        }
                    }
                }
            }
            return -1; // If there's no path to the end
        }

        // Example usage
        const routes = [
            {busRoute: "1", stops: [{stop: "A", time: 0}, {stop: "B", time: 5}, {stop: "C", time: 10}]},
            {busRoute: "2", stops: [{stop: "A", time: 0}, {stop: "D", time: 3}, {stop: "B", time: 8}]},
            {busRoute: "3", stops: [{stop: "B", time: 0}, {stop: "C", time: 4}, {stop: "E", time: 6}]},
            {busRoute: "4", stops: [{stop: "D", time: 0}, {stop: "E", time: 2}, {stop: "B", time: 7}]},
        ];
        const start = "A";
        const end = "B";
        console.log(getFastestBusRoute(routes, start, end)); // Output: 5
    

Conclusion

In this lecture, we learned how to solve the algorithm problem of finding the fastest path based on the given bus route information. Dijkstra's algorithm is a very useful method for finding the shortest path in weighted graphs. I hope you found it helpful to understand how to explore at each step and implement it in code.

Additional Practice Problems

If you've learned Dijkstra's algorithm through this problem, try tackling the following variations:

  • A problem where you must find a route using only specific lines
  • A problem that finds routes based on travel distance instead of travel time between stops
  • A problem that finds the maximum path instead of the minimum path

References

JavaScript Coding Test Course, Calculating Number of Stairs

Hello, today we will solve one of the algorithm problems useful for JavaScript coding test preparation, called “Counting Stair Numbers”. This problem can be approached interestingly using Dynamic Programming and combinatorial methods. In this article, I will provide a detailed explanation including the problem description, the solution process, and optimization strategies.

Problem Description

A stair number refers to a number of n digits where the difference between two adjacent digits is 1. For example, numbers like 123 and 321 are stair numbers since the difference between adjacent digits is 1. Write a program to find the n-digit stair numbers for the given n.

Input

An integer n (1 ≤ n ≤ 1000)

Output

Output the number of n-digit stair numbers modulo 1,000,000,000.

Problem Solving Strategy

To solve this problem, we can use a dynamic programming approach. Stair numbers can be defined by the following state:

  • dp[i][j]: the number of i-digit stair numbers that end with j

The rules for forming stair numbers can be established as follows:

  • When j is 0 (no number can start with 0): dp[i][0] = dp[i-1][1]
  • When j is 9: dp[i][9] = dp[i-1][8]
  • In other cases: dp[i][j] = dp[i-1][j-1] + dp[i-1][j+1]

Initialization of the Dynamic Programming Table

Now let’s initialize the dp table. For 1-digit numbers, since digits can range from 0 to 9, we initialize dp[1][0] to dp[1][9] to 1 respectively.

Solution Code


function countStairNumbers(n) {
    const MOD = 1000000000;
    const dp = Array.from({ length: n + 1 }, () => Array(10).fill(0));

    // Initialize 1-digit numbers
    for (let j = 0; j < 10; j++) {
        dp[1][j] = 1;
    }

    // Fill the dp table
    for (let i = 2; i <= n; i++) {
        for (let j = 0; j < 10; j++) {
            if (j > 0) dp[i][j] += dp[i - 1][j - 1]; // Move from j-1
            if (j < 9) dp[i][j] += dp[i - 1][j + 1]; // Move from j+1
            dp[i][j] %= MOD; // modulo operation
        }
    }

    // Sum of all n-digit stair numbers
    let result = 0;
    for (let j = 0; j < 10; j++) {
        result += dp[n][j];
    }

    return result % MOD;
}

// Example of function call
console.log(countStairNumbers(3)); // 24

Time Complexity

The time complexity of the above code is O(n), and the space complexity is O(n). Since the result is derived through combinations of each digit, it efficiently uses time and space as n increases.

Optimization Strategies

To reduce memory usage in the currently implemented code, we can change the dp array from two-dimensional to one-dimensional. Since only the previous dp state is needed for each i, this can be utilized for optimization.


function countStairNumbersOptimized(n) {
    const MOD = 1000000000;
    const dp = Array(10).fill(0);
    const temp = Array(10).fill(0);

    // Initialize 1-digit numbers
    for (let j = 0; j < 10; j++) {
        dp[j] = 1;
    }

    for (let i = 2; i <= n; i++) {
        for (let j = 0; j < 10; j++) {
            temp[j] = 0;
            if (j > 0) temp[j] += dp[j - 1]; // Move from j-1
            if (j < 9) temp[j] += dp[j + 1]; // Move from j+1
            temp[j] %= MOD; // modulo operation
        }
        for (let j = 0; j < 10; j++) {
            dp[j] = temp[j]; // Update for the next step
        }
    }

    // Sum of all n-digit stair numbers
    let result = 0;
    for (let j = 0; j < 10; j++) {
        result += dp[j];
    }

    return result % MOD;
}

Conclusion

In this article, we learned how to solve the “Counting Stair Numbers” problem using dynamic programming in JavaScript. I provided a detailed explanation of initialization, constructing the dp table, and the optimization process, as well as methods to enhance the efficiency of the algorithm through various techniques. When solving algorithm problems, always consider multiple approaches and explore ways to optimize them. Thank you!

JavaScript Coding Test Course, Kevin Bacon’s Six Degrees of Separation

Hello! In this post, we will explore “The Six Degrees of Kevin Bacon,” one of the topics in the JavaScript coding test, and I will detail the process of solving the problem. In this lecture, we will learn how to design and implement algorithms, as well as how to efficiently use JavaScript syntax and functions.

1. What is the Six Degrees of Kevin Bacon?

The Six Degrees of Kevin Bacon is a theory that describes the relationships among movie actors. According to this theory, the relationship between two people can be achieved through a maximum of six connections. In other words, if actor A is connected to actor B, and B has another connection to C, it is believed that there is a relationship between A and C within six degrees.

2. Problem Description

This problem is based on graph theory. Given a list of actors and their relationships, the task is to find the links between a specific actor and another actor and calculate how many degrees of connection exist between them.

Problem Definition

    Represent the given list of actors and their relationships as a graph,
    and write a function that calculates and returns the number of steps 
    between the two given actors.
    
    Constraints:
    - Each actor is represented as a string, and two actors are directly connected
      if they appeared in the same movie together.
    - The edges of the graph are undirected, allowing us to use BFS 
      (Breadth-First Search) to find the shortest path.
    
    Example input:
    actors = [
        ['A', 'B'],
        ['B', 'C'],
        ['C', 'D'],
        ['D', 'E'],
        ['A', 'E']
    ]
    startActor = 'A'
    targetActor = 'D'
    
    Example output:
    3 (A -> B -> C -> D)
    

3. Solution Method

To solve this problem, we will proceed with the following steps.

3.1. Selecting Data Structure

First, we need a data structure to store the relationships among all actors. We will represent this relationship using a Map or Object as a graph. Each actor will be a key, and the corresponding value will be an array of actors they have appeared with.

3.2. Constructing the Graph

Using the given list of actors, we will add the relationships between actors to the selected data structure.

3.3. Finding the Shortest Path

We’ll use the BFS algorithm to find the shortest path between the starting actor and the target actor. BFS is a useful algorithm for solving shortest distance problems, as it explores nodes level by level and guarantees the shortest path.

4. JavaScript Implementation

Now, based on what has been described above, let’s implement it in JavaScript code.

    function findShortestPath(actors, startActor, targetActor) {
        // Create the graph
        const graph = {};
        
        for (const [actorA, actorB] of actors) {
            if (!graph[actorA]) graph[actorA] = [];
            if (!graph[actorB]) graph[actorB] = [];
            graph[actorA].push(actorB);
            graph[actorB].push(actorA);
        }
        
        // BFS algorithm
        const queue = [[startActor, 0]];
        const visited = new Set();
        visited.add(startActor);
        
        while (queue.length > 0) {
            const [currentActor, steps] = queue.shift();
            
            // If the target actor is reached
            if (currentActor === targetActor) {
                return steps;
            }
            
            for (const neighbor of graph[currentActor]) {
                if (!visited.has(neighbor)) {
                    visited.add(neighbor);
                    queue.push([neighbor, steps + 1]);
                }
            }
        }
        
        // No connection
        return -1;
    }
    
    // Example execution
    const actors = [['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'E'], ['A', 'E']];
    const startActor = 'A';
    const targetActor = 'D';
    
    console.log(findShortestPath(actors, startActor, targetActor)); // Output: 3
    

5. Code Analysis

Through the above code, we have constructed a graph based on the relationships between the given actors and implemented a method to find the shortest path using BFS. Let’s analyze this part in more detail.

5.1. Graph Implementation

The graph is structured as an object, where the key is the actor’s name and the value is an array of connected actors. We update the bidirectional connections directly by adding the associated actors.

5.2. Functioning of BFS

BFS is implemented using a queue. We add the starting actor to the queue and include visited actors in a Set to avoid duplicate visits. We continuously explore until the queue is empty, returning the number of steps taken when the target actor is found.

5.3. Time Complexity

The time complexity of this algorithm is O(V + E), where V represents the number of actors and E represents the number of edges. This ensures efficient performance, allowing for quick results even with large data sets.

6. Conclusion

In this post, we examined the process of solving an algorithm problem using “The Six Degrees of Kevin Bacon.” We covered the entire process from algorithm design to data structure selection, implementation, and analysis. Such problems are frequently featured in actual coding tests, so it is important to practice and master them.

In the future, I will continue to post about various algorithms and problem-solving methods. Always remember to practice coding, and feel free to leave any questions in the comments!

7. Additional Reference Materials

JavaScript Coding Test Course, Pathfinding

Coding tests are taken by many developers, and they are assessed through various algorithm problems. In this article, we will take a closer look at the algorithm problem-solving process through the ‘pathfinding’ problem. This problem is suitable for using graph traversal techniques such as DFS (Depth-First Search) or BFS (Breadth-First Search).

Problem Description

You need to find a path from the starting point to the destination point in a given 2D grid. Each cell of the grid indicates whether it can be moved to or not, and the following are the problem conditions:

  • The grid has dimensions N x M.
  • Each cell is marked with 0 or 1, where 0 indicates a traversable path and 1 indicates a blocked path.
  • The starting point is at (0, 0) and the destination point is at (N-1, M-1).
  • You can move up, down, left, or right.

Example Input

    4 4
    0 0 1 0
    0 1 0 0
    0 1 1 0
    0 0 0 0
    

Example Output

    Yes
    

Problem Solving Process

The algorithm we will use to solve this problem is BFS. BFS is advantageous for finding the shortest path as it explores each node in order. We will solve the problem through the following steps:

1. Create the grid space

Convert the received grid information into an array. This will facilitate access to and movement through each cell.

2. Initialize BFS

Add the starting point (0, 0) to the queue. Additionally, we use an extra array to track visited nodes.

3. Perform BFS search

Repeat the following while the queue is not empty:

  • Dequeue a node to check the current position.
  • Check if the current position is the destination point (N-1, M-1). If so, output ‘Yes’ and terminate the search.
  • For all cells that can be moved to up, down, left, or right, do the following:
    • Check the boundary conditions and whether it has been visited.
    • If the cell’s value is 0 and it has not been visited, add it to the queue and mark it as visited.

If the search completes and the destination point has not been reached, output ‘No’.

4. Code Implementation

    function canReachDestination(grid) {
        const N = grid.length;
        const M = grid[0].length;
        const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]];
        const queue = [[0, 0]];
        const visited = Array.from({length: N}, () => Array(M).fill(false));

        visited[0][0] = true;

        while (queue.length > 0) {
            const [x, y] = queue.shift();

            // Check the destination point
            if (x === N - 1 && y === M - 1) {
                return "Yes";
            }

            for (const [dx, dy] of directions) {
                const nx = x + dx;
                const ny = y + dy;

                // Check boundary conditions and whether visited
                if (nx >= 0 && ny >= 0 && nx < N && ny < M && 
                    !visited[nx][ny] && grid[nx][ny] === 0) {
                    visited[nx][ny] = true;
                    queue.push([nx, ny]);
                }
            }
        }
        return "No";
    }

    const grid = [
        [0, 0, 1, 0],
        [0, 1, 0, 0],
        [0, 1, 1, 0],
        [0, 0, 0, 0]
    ];
    console.log(canReachDestination(grid)); // Result: "Yes"
    

Conclusion

Through this problem, we have solved a pathfinding problem using the BFS algorithm. In actual coding tests, you will often encounter such problems, so it is important to practice various algorithms to develop problem-solving skills for diverse challenges. In the next lesson, we will address different types of algorithm problems. Thank you!