JavaScript Coding Test Course, Try

In this course, we will take a detailed look at solving algorithm problems using JavaScript and the usage of the Trie data structure. The Trie is a very useful data structure to improve the efficiency of string processing. In this course, we will explain the process of solving specific problems using the Trie.

What is a Trie?

A Trie is a tree-like data structure used to store a large number of strings. Common applications include auto-completion, word search, and prefix search. Each node in the Trie corresponds to a character in the string, allowing for efficient word composition through paths.

Problem: Word Search

Below is a problem utilizing the Trie data structure.

When given a list of words and a search term, find all the words in the list that exist, and return all words that contain the search term.

Problem-Solving Strategy

  1. First, implement the Trie structure.
  2. Insert the given list of words into the Trie.
  3. Use the search term to explore all possible words in the Trie.

Trie Implementation

To implement a Trie, the following basic structure is needed:

class TrieNode {
    constructor() {
        this.children = {};
        this.isEndOfWord = false;
    }
}

class Trie {
    constructor() {
        this.root = new TrieNode();
    }

    insert(word) {
        let node = this.root;
        for (let char of word) {
            if (!node.children[char]) {
                node.children[char] = new TrieNode();
            }
            node = node.children[char];
        }
        node.isEndOfWord = true;
    }

    search(prefix) {
        let node = this.root;
        for (let char of prefix) {
            if (!node.children[char]) return [];
            node = node.children[char];
        }
        return this._findAllWords(node, prefix);
    }

    _findAllWords(node, prefix) {
        const results = [];
        if (node.isEndOfWord) {
            results.push(prefix);
        }
        for (let char in node.children) {
            results.push(...this._findAllWords(node.children[char], prefix + char));
        }
        return results;
    }
}

Inserting and Searching Words

Now, I will explain how to insert words into the Trie and find all possible words for a specific search term. The process will be illustrated through the example below:

const getWords = (words, searchWord) => {
    const trie = new Trie();
    for (let word of words) {
        trie.insert(word);
    }
    return trie.search(searchWord);
};

const wordsList = ["apple", "app", "apricot", "banana", "bat", "ball"];
const searchTerm = "ap";
const foundWords = getWords(wordsList, searchTerm);
console.log(foundWords); // ["apple", "app", "apricot"]

Code Explanation

In the above code, the getWords function first inserts the provided list of words into the Trie, then searches the Trie with the given search term. The insert method takes a word and connects each character as a node, while the search method finds and returns all words corresponding to the given prefix.

Complexity Analysis

The performance of insertion and search in the Trie varies depending on the length of the string and the depth of the tree:

  • Insertion: O(L), where L is the length of the word.
  • Search: O(P + W), where P is the length of the prefix, and W is the number of words returned as a result.

Conclusion

In this course, we learned how to solve string search problems using the Trie data structure in JavaScript. Tries have the capability to efficiently handle large numbers of words, making them particularly useful for implementing features like auto-completion or word search.

Explore more examples and problems regarding the Trie algorithm to enhance your understanding. Stay tuned for more algorithms and data structures in the next course!

Javascript Coding Test Course, Finding the Longest Increasing Subsequence

Problem Description

The problem of finding the Longest Increasing Subsequence (LIS) involves finding the longest subsequence within a given sequence while maintaining the order of increasing values. A subsequence does not need to be contiguous, but the chosen numbers must follow an increasing order.

Input Format

  • The first line contains an integer N (1 ≤ N ≤ 1,000), which represents the length of the sequence.
  • The second line contains N integers A1, A2, …, AN (1 ≤ Ai ≤ 1,000,000).

Output Format

Output the length of the longest increasing subsequence.

Example

Input Example

6
10 20 10 30 20 50

Output Example

4

Problem Solving Process

Step 1: Understand the Problem

To understand the problem, let’s examine the sequence. For the given sequence [10, 20, 10, 30, 20, 50], there are several possible increasing subsequences. Among them, the longest increasing subsequence is [10, 20, 30, 50], which has a length of 4. Therefore, the answer is 4.

Step 2: Choose an Algorithm

There are various algorithms for finding the longest increasing subsequence, but the most efficient method is to use dynamic programming. This method has a time complexity of O(N^2). I will use this method to solve the problem.

Step 3: Dynamic Programming Solution

function LIS(array) {
        const N = array.length;
        const dp = new Array(N).fill(1); // Initialize subsequence lengths to 1

        for (let i = 1; i < N; i++) {
            for (let j = 0; j < i; j++) {
                if (array[i] > array[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }

        return Math.max(...dp);
    }

    const sequence = [10, 20, 10, 30, 20, 50];
    console.log(LIS(sequence)); // Output: 4
    

Explanation

1. Declare the dp array to store the length of the longest increasing subsequence for each index. The initial value is set to 1 since each element can form a subsequence by itself.

2. Use two loops to compare indices i and j. If array[i] is greater than array[j], the value of dp[i] is updated to the maximum of dp[j] + 1 and the current value of dp[i]. This considers the subsequence that includes array[j] as well as array[i].

3. After all iterations, the largest value in the dp array will be the length of the longest increasing subsequence.

Result

Executing the above code will successfully determine the length of the longest increasing subsequence in the given sequence.

Conclusion

The Longest Increasing Subsequence (LIS) problem is one of the frequently asked algorithm problems. Through this problem, one can learn the basics of dynamic programming and enhance problem-solving skills in real coding tests. It is important to gain experience by solving various problems.

JavaScript Coding Test Course, Bubble Sort Program 1

Let’s learn about the essential algorithm for coding tests, Bubble Sort.

1. Problem Definition

Implement the Bubble Sort algorithm that takes an array as input and sorts it in ascending order.
Bubble Sort operates by comparing two adjacent elements and repeatedly moving the largest element to the end of the array.

Input Format

The input is an integer array with a length between 1 and 1000. Each element can have a value between -10,000 and 10,000.

Output Format

Return the array sorted in ascending order.

2. Problem Approach

Bubble Sort is a very intuitive sorting algorithm. The basic approach is to compare two adjacent elements,
and if they are not in order, swap them repeatedly until the entire array is sorted. This process is repeated for the size of the array,
and continues until no more swaps occur. This way, the largest value moves to the end of the array in each step.

2.1. Algorithm Steps

  1. Obtain the length of the array.
  2. Use two indices to compare the elements of the array.
  3. If adjacent elements are not sorted, swap them.
  4. Consider the sorting complete if no swaps occur during a full pass.
  5. Repeat the above process and ultimately return the array sorted in ascending order.

3. Bubble Sort Code Implementation

Now let’s implement the above algorithm in JavaScript. The basic Bubble Sort function is as follows.


// Bubble Sort function implementation
function bubbleSort(arr) {
    let n = arr.length;  // Store the length of the array

    // Repeat to sort the array
    for (let i = 0; i < n - 1; i++) {
        let swapped = false;  // Variable to check if a swap has occurred

        // Compare and swap adjacent elements
        for (let j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // Swap
                let temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;  // Record that a swap has occurred
            }
        }

        // Exit if no swaps have occurred
        if (!swapped) break;
    }

    return arr;  // Return the sorted array
}

// Test
let testArray = [64, 34, 25, 12, 22, 11, 90];
console.log(bubbleSort(testArray));  // [11, 12, 22, 25, 34, 64, 90]

        

4. Time Complexity Analysis

The time complexity of the Bubble Sort algorithm is O(n²) in the worst case. This is due to the presence of two nested loops, each proportional to the length of the array.
The best case (when the array is already sorted) is O(n). In this case, no swaps occur, and the process terminates after the first step.
Bubble Sort is generally inefficient, and it is advisable to use other algorithms (e.g., Quick Sort, Merge Sort) when sorting large datasets in practice.

4.1. Space Complexity

The space complexity of Bubble Sort is O(1). It does not use any unnecessary additional memory,
as sorting is performed within the given array.

5. Advantages and Disadvantages of Bubble Sort

Advantages

  • The algorithm is simple and easy to understand.
  • It does not have any specific requirements, so no additional memory management is necessary.
  • It works effectively with small datasets.

Disadvantages

  • The time complexity is inefficient (O(n²)).
  • Even when the array is well sorted, it must perform a complete pass, reducing efficiency.
  • It is inefficient for sorting large datasets.

6. Conclusion

In this lecture, we implemented the Bubble Sort algorithm using JavaScript.
Due to its structural simplicity, Bubble Sort is useful for educational purposes, but in real production environments, it is advisable to use more efficient algorithms.
I hope you can further develop your coding skills as you explore more complex algorithms and data structures in the future.

7. References

JavaScript Coding Test Course, Counting the Number of Connected Components

In this course, we will explore the problem of ‘Counting the Number of Connected Components’, which is frequently presented in coding tests, and explain the algorithmic approach to solve it in detail. We will help you become familiar with JavaScript through in-depth learning with various examples.

Problem Description

This problem involves counting the number of connected components in a given undirected graph. An undirected graph consists of vertices (v) and edges (e) and represents the connectivity between vertices. That is, if there is an edge between two vertices, then these two vertices are directly connected. The connected components of a graph refer to a set of vertices that cannot be connected further. In this problem, there can be multiple sets, and the vertices within each set can reach each other but cannot reach vertices in other sets. For example, consider the following graph.

          0 --- 1     3 --- 4
                |
                2
        

In this graph, there are two connected components: {0, 1, 2} and {3, 4}. Therefore, the number of connected components is 2.

Input Format

The function takes two parameters:

  • n: Number of vertices (0 ≤ n ≤ 1000)
  • edges: A list of edges, where each edge is an array consisting of the vertex numbers. Example: [[0,1], [1,2], [3,4]]

Output Format

Returns the number of connected components.

Examples

          Input: n = 5, edges = [[0,1], [1,2], [3,4]]
          Output: 2

          Input: n = 6, edges = [[0,1], [0,2], [1,3]]
          Output: 3
        

Solution Method

To calculate the number of connected components, we can use the DFS (Depth First Search) algorithm. DFS starts from one vertex and explores adjacent vertices deeply, visiting unvisited vertices. By utilizing this algorithm, we can traverse the graph and identify connected components. The steps to implement this are as follows:

  1. Graph Creation: Convert the graph from the information of vertices and edges into an adjacency list format.
  2. Create a Visited Array: Create an array to check if each vertex has been visited.
  3. Implement DFS: Use a recursive function to implement DFS traversal, checking all vertices connected to each vertex upon visiting it.
  4. Count Connected Components: Visit all vertices, increasing the count of connected components every time a new starting vertex is discovered.

JavaScript Code Implementation

            
            function countConnectedComponents(n, edges) {
                // Convert the graph to an adjacency list
                const graph = Array.from({length: n}, () => []);
                edges.forEach(([u, v]) => {
                    graph[u].push(v);
                    graph[v].push(u);
                });

                const visited = new Array(n).fill(false);
                let count = 0;

                function dfs(node) {
                    visited[node] = true;
                    for (const neighbor of graph[node]) {
                        if (!visited[neighbor]) {
                            dfs(neighbor);
                        }
                    }
                }

                for (let i = 0; i < n; i++) {
                    if (!visited[i]) {
                        dfs(i);
                        count++; // Increase count every time a new connected component is found
                    }
                }

                return count;
            }

            // Example execution
            console.log(countConnectedComponents(5, [[0,1],[1,2],[3,4]])); // Output: 2
            console.log(countConnectedComponents(6, [[0,1],[0,2],[1,3]])); // Output: 3
            
        

Complexity Analysis

The time complexity of this algorithm is O(V + E), where V is the number of vertices and E is the number of edges. This is due to the fact that we visit all vertices and edges in the graph. The space complexity is also O(V), which includes the space used to store the visited array and the graph.

Conclusion

In this course, we have implemented an algorithm to count the number of connected components using JavaScript. Graph theory is a very important topic in coding tests, and it is crucial to practice problems related to it thoroughly to build your skills. We hope this helps deepen your understanding through various examples and fosters your own algorithmic thinking.

JavaScript Coding Test Course, Finding the Order of Building

This article will discuss the ‘Building Order Problem’ among JavaScript coding test questions. This problem is an interesting one that can be solved using concepts from graph theory and topological sorting. Before we begin, let’s review the problem’s definition and requirements.

Problem Definition

The problem is to determine the order in which all buildings must be constructed based on the given N buildings and their dependencies. If building A must be constructed before building B, then there is a dependency relationship between these two buildings.

Input


Input is in the following format:
- First line: N (number of buildings), M (number of dependencies)
- Next M lines: A B (indicates that building A must be constructed before building B)
    

Output


Print a possible construction order of buildings, separated by spaces in one line. If the order is not possible, print "Impossible".
    

Examples

Example 1


Input:
4 2
1 2
2 3

Output:
1 2 3 4
    

Example 2


Input:
3 3
1 2
2 3
3 1

Output:
Impossible
    

Problem Solving Process

Topological Sorting

The most important concept for solving this problem is topological sorting. Topological sorting is a method of ordering the vertices of a directed graph considering their precedence relationships. For a topological sorting result to exist, the graph must not contain cycles. In other words, all dependencies must be clear to determine the order.

Problem Solving Algorithm

The algorithm to solve the problem can be structured as follows.

  1. Read the number of vertices (N) and the number of edges (M) in the graph.
  2. Create an adjacency list for the graph while counting the number of buildings required to be constructed for each building (in-degree).
  3. Add buildings with an in-degree of 0 to the queue.
  4. Remove one building at a time from the queue, add it to the result list, and decrease the in-degree of the buildings that depend on it.
  5. Add buildings whose in-degree becomes 0 to the queue.
  6. After processing all buildings, if the length of the result list equals N, print the possible construction order; otherwise, print “Impossible”.

JavaScript Code Implementation


function getConstructionOrder(N, M, dependencies) {
    const graph = Array.from({ length: N + 1 }, () => []);
    const indegree = Array.from({ length: N + 1 }, () => 0);
    
    // Add dependencies to the graph
    for (const [a, b] of dependencies) {
        graph[a].push(b);
        indegree[b]++;
    }
    
    const queue = [];
    
    // Add nodes with in-degree of 0
    for (let i = 1; i <= N; i++) {
        if (indegree[i] === 0) {
            queue.push(i);
        }
    }
    
    const order = [];
    
    while (queue.length > 0) {
        const current = queue.shift();
        order.push(current);
        
        for (const neighbor of graph[current]) {
            indegree[neighbor]--;
            if (indegree[neighbor] === 0) {
                queue.push(neighbor);
            }
        }
    }

    return order.length === N ? order : "Impossible";
}

// Test example
const N = 4;
const M = 2;
const dependencies = [[1, 2], [2, 3]];
const result = getConstructionOrder(N, M, dependencies);
console.log(result.join(' ')); // Output: 1 2 3 4
    

Conclusion

In this tutorial, we learned the concept of topological sorting and how to solve the ‘Building Order Problem’ in JavaScript. Through the process of constructing a graph based on arbitrary input and deriving the possible construction order from it, we hope to enhance your algorithm problem-solving skills. Similar problems may appear in various technical interviews, so continuous practice and understanding are necessary. Thank you!

References

If you wish to deepen your understanding of related materials and algorithms, please refer to the resources below.