JavaScript Coding Test Course, Extended Euclidean Algorithm

Hello, everyone! Today, we will learn about one of the important algorithms in coding tests using JavaScript, the Extended Euclidean Algorithm. In this course, we will provide the concept of the Extended Euclidean Algorithm, the problem-solving process using it, and practical code examples.

1. Problem Description

We will define the problem as follows. Given two integers A and B, the task is to find integers X and Y that satisfy AX + BY = GCD(A, B). Here, GCD refers to the greatest common divisor.

Example

Input: A = 30, B = 21

Output: X = 1, Y = -1, GCD = 3

Solution: 30 * 1 + 21 * (-1) = 3

2. Concept Explanation

The Extended Euclidean Algorithm not only calculates the greatest common divisor (GCD) of two integers but also finds specific coefficients from it. This is primarily used in the following formula:

AX + BY = GCD(A, B)

Here, A and B are the given two integers, and X and Y are the integers we want to find. If the GCD is 1, A and B are coprime, and X and Y can also be used to find modular inverses.

3. Approach

We will implement the Extended Euclidean Algorithm based on the general Euclidean algorithm for finding GCD. The main idea of the algorithm is as follows:

  1. Receive two integers A and B as input.
  2. If B is 0, then the GCD is A, and X is 1, Y is 0.
  3. Otherwise, recursively call with B and A % B using the Euclidean algorithm.
  4. Use the results of the recursive call to calculate the values of X and Y.

4. Algorithm Implementation

Below is an example of implementing the Extended Euclidean Algorithm in JavaScript:


function extendedGCD(a, b) {
    if (b === 0) { // Base case
        return { gcd: a, x: 1, y: 0 };
    }
    // Recur with the new parameters b and a % b
    const { gcd, x: x1, y: y1 } = extendedGCD(b, a % b);
    const x = y1;
    const y = x1 - Math.floor(a / b) * y1;
    return { gcd, x, y };
}

// Test the function with example values
const a = 30;
const b = 21;
const { gcd, x, y } = extendedGCD(a, b);
console.log(`GCD: ${gcd}, X: ${x}, Y: ${y}`);

5. Code Explanation

In the above code, we are recursively calculating the GCD. In the base case, when B is 0, the GCD is A, and at that point, X is 1, Y is 0. After that, we calculate new values for X and Y using the returned X and Y values to ultimately get the result we desire.

6. Test Cases

Now let’s test the function with various test cases.


// Test cases
const testCases = [
    { a: 30, b: 21 },
    { a: 48, b: 18 },
    { a: 56, b: 15 },
    { a: 101, b: 10 },
];

testCases.forEach(({ a, b }) => {
    const { gcd, x, y } = extendedGCD(a, b);
    console.log(`A: ${a}, B: ${b} => GCD: ${gcd}, X: ${x}, Y: ${y}`);
});

7. Conclusion

Today, we learned about the Extended Euclidean Algorithm. This algorithm is very useful for finding the greatest common divisor of two integers and for finding specific coefficients related to it. It is especially used in modular arithmetic and complex algorithm problems, so it is important to understand and practice it thoroughly.

I hope the algorithm used in this article will help you in your coding exam preparations. If you have any additional questions, please leave them in the comments!

JavaScript Coding Test Course, Traversing Trees

Overview

In coding tests, various data structure and algorithm problems are presented. Among them, trees are a commonly occurring data structure.
Tree structures play a very important role in computer science and are utilized in various fields such as file systems and databases.
In this course, we will learn how to traverse trees using JavaScript.

What is a Tree Structure?

A tree is a nonlinear data structure composed of nodes and edges, optimized for representing hierarchical relationships.
A tree has concepts such as root node, child node, parent node, and leaf node.

The main characteristics of a tree are as follows:

  • A tree has one root, and child nodes are connected from this root.
  • A node can have zero or more child nodes.
  • A leaf node is a node that has no children.

Tree Traversal Methods

There are several ways to traverse a tree, with the most commonly used methods being:

  • Pre-order Traversal
  • In-order Traversal
  • Post-order Traversal
  • Level-order Traversal

The order in which nodes are visited differs for each traversal method. Let’s take a closer look at each method.

Pre-order Traversal

The method of pre-order traversal is as follows:

  1. Visit the current node.
  2. Traverse the left subtree in pre-order.
  3. Traverse the right subtree in pre-order.

For example, suppose we have the following tree structure.

                Public
                ├── User 1
                │   ├── User 1.1
                │   └── User 1.2
                └── User 2
                    ├── User 2.1
                    └── User 2.2
                

The result of the pre-order traversal is “Public, User 1, User 1.1, User 1.2, User 2, User 2.1, User 2.2”.

In-order Traversal

The method of in-order traversal is as follows:

  1. Traverse the left subtree in in-order.
  2. Visit the current node.
  3. Traverse the right subtree in in-order.

For example, in the same tree structure, the result of the in-order traversal is “User 1.1, User 1, User 1.2, Public, User 2.1, User 2, User 2.2”.

Post-order Traversal

The method of post-order traversal is as follows:

  1. Traverse the left subtree in post-order.
  2. Traverse the right subtree in post-order.
  3. Visit the current node.

In the same tree structure, the result of the post-order traversal is “User 1.1, User 1.2, User 1, User 2.1, User 2.2, User 2, Public”.

Level-order Traversal

The method of level-order traversal is as follows:

  1. Visit the root node.
  2. Visit the child nodes of the current node.
  3. After visiting all child nodes, move to the next depth.

In the same tree structure, the result of the level-order traversal is “Public, User 1, User 2, User 1.1, User 1.2, User 2.1, User 2.2”.

Programming Problem: Binary Tree Traversal

Given the following binary tree structure, write a function to traverse the tree using various traversal methods.
A binary tree is composed of nodes structured as follows:

            class TreeNode {
                constructor(value) {
                    this.value = value;
                    this.left = null;
                    this.right = null;
                }
            }
            

Example Input:

            const root = new TreeNode(1);
            root.left = new TreeNode(2);
            root.right = new TreeNode(3);
            root.left.left = new TreeNode(4);
            root.left.right = new TreeNode(5);
            

Problem

Write a function for pre-order, in-order, post-order, and level-order traversal of the binary tree above.

Problem Solving Process

1. Implementing Pre-order Traversal

To perform pre-order traversal, a recursive approach is needed. Below is the code that implements this:

            function preOrderTraversal(node) {
                if (node === null) return;
                console.log(node.value); // Visit current node
                preOrderTraversal(node.left); // Visit left subtree
                preOrderTraversal(node.right); // Visit right subtree
            }
            

The above code visits the current node first and then traverses the left and right nodes.

2. Implementing In-order Traversal

In-order traversal is also implemented recursively. Below is the in-order traversal code:

            function inOrderTraversal(node) {
                if (node === null) return;
                inOrderTraversal(node.left); // Visit left subtree
                console.log(node.value); // Visit current node
                inOrderTraversal(node.right); // Visit right subtree
            }
            

This code visits the left subtree first and then the current node.

3. Implementing Post-order Traversal

Post-order traversal is also implemented recursively. Below is the implemented code:

            function postOrderTraversal(node) {
                if (node === null) return;
                postOrderTraversal(node.left); // Visit left subtree
                postOrderTraversal(node.right); // Visit right subtree
                console.log(node.value); // Visit current node
            }
            

In post-order traversal, the current node is visited after both child subtrees.

4. Implementing Level-order Traversal

Level-order traversal is implemented using a queue data structure. By using a queue, each node can be visited layer by layer. Below is the level-order traversal code:

            function levelOrderTraversal(root) {
                if (root === null) return;
                const queue = [root]; // Initialize the queue
                while (queue.length > 0) {
                    const current = queue.shift(); // Remove node from the queue
                    console.log(current.value); // Visit current node
                    if (current.left) queue.push(current.left); // Add left child
                    if (current.right) queue.push(current.right); // Add right child
                }
            }
            

Using a queue allows each node to be visited in order by level.

Conclusion

In this course, we explored various methods of traversing trees using JavaScript.
Tree traversal is a fundamental part of many programming problems, so it’s important to practice sufficiently.
Understanding and implementing the pre-order, in-order, post-order, and level-order traversal algorithms covered above is a great way to achieve good results in coding tests.

Continue to solve various algorithm problems through practice. Practice and repetition are the best teachers!

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!