JavaScript Coding Test Course, Jumong’s Command

Hello! In this post, we will solve an algorithm problem based on the theme “Command of Jumong” for those preparing for coding tests with JavaScript. Throughout this process, we’ll thoroughly explain how to understand the problem, the approach, code implementation, and time complexity analysis.

Problem Description

Despite the winter, Jumong issued commands to his subordinates. His commands are given in the following format:

  • A binary tree is given.
  • Each node contains an integer from 0 to n.
  • When a number x commanded by Jumong is provided, we need to count the occurrences of x in all paths of the binary tree.

In this problem, we must clearly understand what a path in the tree is and how to traverse it. A path refers to the set of all nodes moving from the root to a leaf node.

Input Format

{
    "root": {
        "value": 1,
        "left": {
            "value": 2,
            "left": null,
            "right": {
                "value": 3,
                "left": null,
                "right": null
            }
        },
        "right": {
            "value": 4,
            "left": null,
            "right": null
        }
    },
    "x": 1
}

Output Format

Return the sum of the occurrences of x in all paths containing the number x in the given binary tree.

{
    "result": 2
}

Approach to the Problem

To solve this problem, we will use the DFS (Depth-First Search) algorithm. It involves counting `x` while traversing the binary tree in a depth-first manner along the paths.

Step 1: Tree Traversal

First, we write a recursive function that can traverse the tree. This function takes the current node, the count of x included in the current path, and a variable to store the result as arguments.

Step 2: Save Path and Check Conditions

We check if the node is a leaf node, and when we reach a leaf node, we add the count of x included in the path to the result variable. If the current node is not a leaf, we recursively traverse the left and right child nodes.

Step 3: Time Complexity Analysis

The time complexity of this algorithm is O(n) since we visit each node in the tree once.

Code Implementation


function countXPaths(root, x) {
    let totalPathsCount = 0;

    function dfs(node, currentCount) {
        if (node === null) {
            return;
        }

        // Check the value of the current node
        let newCount = currentCount + (node.value === x ? 1 : 0);

        // Check if it's a leaf node
        if (node.left === null && node.right === null) {
            totalPathsCount += newCount;
            return;
        }

        // Traverse the left and right child nodes
        dfs(node.left, newCount);
        dfs(node.right, newCount);
    }

    dfs(root, 0);
    return { result: totalPathsCount };
}

Test Cases

Now, let’s create various test cases to ensure that the code we wrote works as expected.


// Test Case 1
const testCase1 = {
    root: {
        value: 1,
        left: {
            value: 2,
            left: null,
            right: {
                value: 1,
                left: null,
                right: null
            }
        },
        right: {
            value: 4,
            left: null,
            right: null
        }
    },
    x: 1
};
console.log(countXPaths(testCase1.root, testCase1.x)); // Output: { result: 2 }

// Test Case 2
const testCase2 = {
    root: {
        value: 2,
        left: {
            value: 1,
            left: null,
            right: null
        },
        right: {
            value: 1,
            left: {
                value: 3,
                left: null,
                right: null
            },
            right: null
        }
    },
    x: 1
};
console.log(countXPaths(testCase2.root, testCase2.x)); // Output: { result: 3 }

// Test Case 3
const testCase3 = {
    root: {
        value: 5,
        left: {
            value: 1,
            left: {
                value: 1,
                left: null,
                right: null
            },
            right: {
                value: 2,
                left: null,
                right: null
            }
        },
        right: {
            value: 7,
            left: null,
            right: null
        }
    },
    x: 1
};
console.log(countXPaths(testCase3.root, testCase3.x)); // Output: { result: 2 }

Conclusion

In this post, we understood and implemented the concepts of depth-first search and recursive functions through the algorithm problem called “Command of Jumong.” By creating various test cases, we increased the reliability of the code and systematically organized how to solve the problem step by step.

The mindset for approaching problems and implementation skills are very important in the process of preparing for coding tests. Continuously practicing will help you become familiar with solving such problems.

JavaScript Coding Test Course, Solving the Traveling Salesman Problem

Hello! In this tutorial, we will cover one of the frequently asked problems in coding tests, the Traveling Salesman Problem (TSP). This problem helps to understand important concepts in graphs and dynamic programming.

1. Problem Description

The Traveling Salesman Problem is the problem of finding the minimum cost path that visits all given cities once and returns to the starting city. This problem is typically represented by a distance matrix that provides the cost of moving between cities, and the salesman must visit all cities exactly once.

2. Mathematical Representation of the Problem

Let the number of cities be represented as N. The distance matrix between cities is defined in the form of cost[i][j]. Here, cost[i][j] represents the cost of moving from city i to city j. The path that the salesman must take can be expressed as follows:

min(cost[0][1] + cost[1][2] + ... + cost[N-1][0])

3. Approach to Solve the Problem

Since the Traveling Salesman Problem is NP-hard, the time complexity is very high for solving it using brute force. Therefore, we can solve the problem more efficiently using dynamic programming.

To solve this problem using a dynamic programming approach, we will use bit masking. Bit masking helps to easily check the visiting status by representing each city as a bit. Let’s approach the problem using the following algorithmic steps.

3.1 State Representation through Bit Masking

We represent the state of visited cities as a bitmask. For example, if there are 4 cities:

  • 0000: No city visited
  • 0001: City 0 visited
  • 0010: City 1 visited
  • 0011: Cities 0 and 1 visited
  • …All combinations can be expressed in this way.

3.2 Definition of the Dynamic Programming Table

The DP table dp[mask][i] stores the minimum cost of visiting the cities corresponding to mask and starting from city i. In the initial state, mask is set to 1, and all other states are initialized to infinity (INFINITY).

4. Algorithm Implementation

Now, let’s implement the algorithm we wrote in JavaScript.

function tsp(cost) {
    const N = cost.length;
    const INF = Number.MAX_SAFE_INTEGER;
    const dp = new Array(1 << N).fill(null).map(() => new Array(N).fill(INF));
    
    // Starting point
    dp[1][0] = 0;

    for (let mask = 0; mask < (1 << N); mask++) {
        for (let u = 0; u < N; u++) {
            if (dp[mask][u] === INF) continue; // Skip if u is not visited

            // Visit all other cities not in the current mask
            for (let v = 0; v < N; v++) {
                if (mask & (1 << v)) continue; // Skip if v is already visited

                const nextMask = mask | (1 << v);
                dp[nextMask][v] = Math.min(dp[nextMask][v], dp[mask][u] + cost[u][v]);
            }
        }
    }

    let ans = INF;
    for (let i = 1; i < N; i++) {
        ans = Math.min(ans, dp[(1 << N) - 1][i] + cost[i][0]);
    }

    return ans;
}

5. Time Complexity

The time complexity of this algorithm is O(N^2 * 2^N). Since the method of representing states through bit masking and the process of updating the DP table are combined, significant computation time is required as the number of cities increases. Therefore, this algorithm is only practical when N is less than or equal to 20.

6. Testing and Examples

Now, let's provide some examples to test the algorithm. Below is the cost matrix between cities:

const costMatrix = [
    [0, 10, 15, 20],
    [10, 0, 35, 25],
    [15, 35, 0, 30],
    [20, 25, 30, 0]
];
console.log(tsp(costMatrix)); // Expected output: 80

7. Conclusion

By dealing with the Traveling Salesman Problem, we gained an understanding of the pathfinding techniques using dynamic programming and bit masking. This problem may seem like a simple route-finding problem, but it is an important algorithmic problem that can be applied in various fields, such as route optimization for internet companies.

I hope this tutorial has been helpful in solving algorithmic problems using JavaScript, and I encourage you to solve more problems through additional practice. Thank you!

JavaScript Coding Test Course, Finding the Diameter of a Tree

1. Problem Description

The diameter of a tree refers to the length of the longest path in the tree. A single tree is a connected set of nodes, with each node branching out from a root node.
The problem of finding the diameter of a tree frequently appears in graph theory and is primarily solved using Depth First Search (DFS) or Breadth First Search (BFS).
In this course, we will explore how to find the diameter of a tree and how to implement it using JavaScript.

2. Example Input and Output

Input

        7
        1 2 3
        1 3 4
        2 4 3
        2 5 2
        3 6 1
        3 7 3
        

Output

7

3. Problem Approach

One way to find the diameter of a tree is to use two rounds of DFS or BFS as follows:

  1. Start from a random node and find the most distant node. We will call this node a temporary node.
  2. Starting from the temporary node, perform DFS or BFS again to find the distance to the furthest node. This distance is the diameter of the tree.

4. JavaScript Implementation

Now, let’s write the code to find the diameter of the tree in JavaScript using the above method. Before we begin, we need to define a simple data structure.


    class TreeNode {
        constructor(value) {
            this.value = value;
            this.children = [];
        }
    }

    class Tree {
        constructor() {
            this.root = null;
        }

        addEdge(parentValue, childValue) {
            const parentNode = this.findNode(this.root, parentValue);
            const childNode = new TreeNode(childValue);
            if (parentNode) {
                parentNode.children.push(childNode);
            } else {
                this.root = parentNode; // If root is null, set it as the first node
            }
        }

        findNode(node, value) {
            if (!node) return null;
            if (node.value === value) return node;
            for (const child of node.children) {
                const result = this.findNode(child, value);
                if (result) return result;
            }
            return null;
        }

        diameter() {
            return this.diameterHelper(this.root).diameter;
        }

        diameterHelper(node) {
            if (!node) return { height: 0, diameter: 0 };

            let maxHeight1 = 0, maxHeight2 = 0, diameter = 0;

            for (const child of node.children) {
                const { height, diameter: childDiameter } = this.diameterHelper(child);
                diameter = Math.max(diameter, childDiameter);
                if (height > maxHeight1) {
                    maxHeight2 = maxHeight1;
                    maxHeight1 = height;
                } else if (height > maxHeight2) {
                    maxHeight2 = height;
                }
            }

            diameter = Math.max(diameter, maxHeight1 + maxHeight2);
            return { height: maxHeight1 + 1, diameter };
        }
    }
    

5. Code Explanation

The code above includes the functionality to find the diameter of a tree. Let’s explain the main parts.

  • TreeNode Class: Represents each node of the tree. It includes the node value and a list of the node’s children.
  • Tree Class: This class manages the tree. It includes the addEdge method to add child nodes and the findNode method to find a node with a given value.
  • diameter Method: Calls the diameterHelper function to calculate the tree’s diameter.
  • diameterHelper Function: Recursively computes the maximum height at each node and updates the diameter at the current node.

6. Performance Analysis

The time complexity of this algorithm is O(n) as it visits every node in the tree once.
The space complexity is O(h) in the worst case because stack frames are used up to the maximum height of the tree, where h signifies the height of the tree.

7. Conclusion

We have explored how to find the diameter of a tree using JavaScript. Problems of this type frequently appear in coding tests, so
it is beneficial to practice many example problems to become familiar with them.
Understanding and utilizing tree structures and DFS/BFS search methods is important, and these fundamental concepts can be applied in various contexts.

JavaScript Coding Test Course, Calculating Interval Products

One of the problems often presented in coding interviews is the problem of finding the product of a specific range (subarray) of an array. In this course, we will define this as the Range Product Problem and explain how to solve it in detail.

Problem Definition

Given an array A and a list of queries, each query includes a range [L, R], and we need to calculate the product from A[L] to A[R]. Let’s assume the given array A and queries are as follows:

A = [2, 3, 4, 5, 6]
Queries = [(1, 3), (0, 2), (2, 4)] // Indices start from 0

We need to output the result of each query. For example, for the query (1, 3), A[1] * A[2] * A[3] = 3 * 4 * 5 = 60.

Understanding the Problem

To solve this problem, we need to understand a few conditions of the problem:

  • Validation of the size of array A and the range (L, R) of each query
  • How to quickly calculate the product of the range
  • How to address the inefficiency issue of calculating multiple products when each element of the array is given

Solution Strategy

To efficiently calculate the range product, we can utilize the prefix product. This method generates a new array P(A) from the original array A, storing the product from index 0 to index i for each index i. Then the product for a query (L, R) can be calculated as follows.

Range Product Q(L, R) = P(R) / P(L - 1)

Here, P(i) represents the product up to index i, and P(0) is A[0].

Implementation

Now, let’s implement the above strategy in JavaScript:


function productRange(arr, queries) {
    // Array size
    const n = arr.length;

    // Initialize the prefix product array
    const prefixProduct = new Array(n);
    prefixProduct[0] = arr[0];

    // Calculate the prefix product
    for (let i = 1; i < n; i++) {
        prefixProduct[i] = prefixProduct[i - 1] * arr[i];
    }

    // Initialize the result array
    const result = [];

    // Process the queries
    queries.forEach(([L, R]) => {
        if (L === 0) {
            result.push(prefixProduct[R]);
        } else {
            result.push(prefixProduct[R] / prefixProduct[L - 1]);
        }
    });

    return result;
}

// Test
const A = [2, 3, 4, 5, 6];
const queries = [[1, 3], [0, 2], [2, 4]];
console.log(productRange(A, queries)); // Expected result: [60, 24, 120]

Analysis

The time complexity of the above code is O(N + Q), where N is the size of the array and Q is the number of queries. This is because calculating the prefix product takes O(N) time, and processing each query takes O(1) time.

This approach allows us to efficiently solve the range product problem, making it useful for query processing under various conditions.

Alternative Method

If there are changes made to the array, an efficient data structure may be needed for updates or dynamically increasing queries. In this case, a data structure such as a segment tree can be utilized. This allows both updates and query processing to be done in O(log N) time.

Conclusion

In this course, we discussed the problem of calculating range products in JavaScript. We demonstrated how to efficiently solve the problem using the prefix product and mentioned that it can be extended into more complex structures if necessary.

Try using these techniques to solve your own coding problems!

JavaScript Coding Test Course, String Search

Problem Description

There is a given string text and a string pattern that needs to be found.
Write a function that returns the position where pattern first appears in text.
If the pattern does not exist in text, return -1.

Input Example

  • text: “hello world”
  • pattern: “world”

Output Example

  • Result: 6 (the string “world” starts at index 6)

Problem Solving Strategy

To solve this problem, we need to check if a specific pattern exists within the string and,
if it does, find its starting index. There are various algorithms for string searching, but
in this tutorial, we will use the most basic and intuitive method called ‘Brute Force’ and
a more efficient algorithm called ‘KMP (Knuth-Morris-Pratt)’.
Let’s first take a look at the Brute Force method.

Brute Force Approach

The Brute Force method compares all combinations of the given string and the pattern to be found.
This method is simple and easy to understand, but in the worst case, its time complexity is O(n*m),
where n is the length of the text and m is the length of the pattern.

Algorithm Steps

  1. Set the variable for the length of the text (n) and the length of the pattern (m).
  2. Increase the starting position of the text one by one and compare with the pattern from the current position.
  3. If all character comparisons match, return the current starting index.
  4. If all characters are compared and the pattern is not found, return -1.

JavaScript Code Implementation


function findFirstOccurrence(text, pattern) {
    const n = text.length;
    const m = pattern.length;

    for (let i = 0; i <= n - m; i++) {
        let j;
        for (j = 0; j < m; j++) {
            if (text[i + j] !== pattern[j]) {
                break;
            }
        }
        if (j === m) {
            return i; // Pattern found
        }
    }
    return -1; // Pattern not found
}

// Test examples
console.log(findFirstOccurrence("hello world", "world")); // 6
console.log(findFirstOccurrence("hello world", "abc")); // -1
    

KMP Algorithm

While the Brute Force method is simple, it can be inefficient. The KMP algorithm improves performance by
preventing unnecessary re-inspections during the search. The basic concept of the KMP algorithm is
‘when part of the pattern matches, reuse the remainder’.

Principle of the KMP Algorithm

The KMP algorithm optimizes string searching using a ‘partial match table (or failure function)’.
This table provides information that can be cached during the search.
The time complexity of the KMP algorithm is O(n + m), making it effective for large datasets.

Algorithm Steps

  1. Create a partial match table for the pattern.
  2. While comparing text and pattern, if there is a mismatch, refer to the table to specify the pattern’s position.
  3. Repeat until a match is found, and ultimately return the index.

Creating the Partial Match Table

The algorithm for creating the partial match table is as follows. This table is used to adjust
the index for the next comparison based on the same prefixes and suffixes from the previously examined string.


function buildKMPTable(pattern) {
    const m = pattern.length;
    const lps = new Array(m).fill(0);
    let len = 0; 
    let i = 1;

    while (i < m) {
        if (pattern[i] === pattern[len]) {
            len++;
            lps[i] = len;
            i++;
        } else {
            if (len !== 0) {
                len = lps[len - 1];
            } else {
                lps[i] = 0;
                i++;
            }
        }
    }
    return lps;
}
    

KMP Algorithm Code Implementation


function KMPSearch(text, pattern) {
    const n = text.length;
    const m = pattern.length;
    const lps = buildKMPTable(pattern);
    let i = 0; // Text index
    let j = 0; // Pattern index

    while (i < n) {
        if (pattern[j] === text[i]) {
            i++;
            j++;
        }
        if (j === m) {
            return i - j; // Pattern found
        } else if (i < n && pattern[j] !== text[i]) {
            if (j !== 0) {
                j = lps[j - 1];
            } else {
                i++;
            }
        }
    }
    return -1; // Pattern not found
}

// Test examples
console.log(KMPSearch("hello world", "world")); // 6
console.log(KMPSearch("hello world", "abc")); // -1
    

Conclusion

In this tutorial, we explored two algorithms for solving the string search problem,
the Brute Force method, and the KMP algorithm.
The Brute Force method is intuitive and straightforward but can be inefficient when searching large strings.
In contrast, the KMP algorithm provides a more efficient way to search for patterns.
Understanding and appropriately utilizing these diverse algorithms is important in real coding tests.

Problems related to string searching are frequently featured in coding tests, so
it’s necessary to gain experience by solving various examples.
Keep learning different algorithm problems to further enhance your skills.