JavaScript Coding Test Course, Implementing Absolute Value Heap

In this course, we will learn how to implement an Absolute Value Heap in JavaScript. An Absolute Value Heap is a heap structure sorted by two criteria, primarily arranging the data based on absolute values. For instance, if the absolute values are the same, the actual values are sorted accordingly.

Problem Description

We will solve an algorithm problem by constructing an Absolute Value Heap and performing the given tasks. The problem is as follows.

Given an array of integers, perform the operation of deleting and returning the number with the smallest absolute value using the Absolute Value Heap. In cases where the absolute values are the same, the smaller actual value is deleted first.

Input Format


["I 5", "I -5", "I 3", "I -2", "I 0", "I 4", "D 1", "D 1"]
    

Output Format


[0, -2, 3, 4]
    

Problem Solving Process

To implement an Absolute Value Heap, we can use JavaScript arrays. We declare an array and implement methods for insertion and deletion based on this array.

Step 1: Define the Heap Class

First, we will design the basic structure of the heap. The heap must always maintain a specific relationship between parent nodes and child nodes according to certain rules. It is sorted in order of increasing absolute value, and in cases of equal absolute values, it is sorted in order of the original values.


class AbsoluteValueHeap {
    constructor() {
        this.heap = [];
    }

    insert(value) {
        this.heap.push(value);
        this.bubbleUp(this.heap.length - 1);
    }

    bubbleUp(index) {
        const element = this.heap[index];
        while (index > 0) {
            const parentIndex = Math.floor((index - 1) / 2);
            const parent = this.heap[parentIndex];
            if (this.isCorrectOrder(element, parent)) break;
            this.heap[index] = parent;
            index = parentIndex;
        }
        this.heap[index] = element;
    }

    isCorrectOrder(child, parent) {
        if (Math.abs(child) < Math.abs(parent)) return true;
        if (Math.abs(child) > Math.abs(parent)) return false;
        return child < parent;
    }

    delete() {
        if (this.heap.length === 0) return null;
        const min = this.heap[0];
        const end = this.heap.pop();
        if (this.heap.length > 0) {
            this.heap[0] = end;
            this.bubbleDown(0);
        }
        return min;
    }

    bubbleDown(index) {
        const element = this.heap[index];
        const length = this.heap.length;
        while (true) {
            let leftChildIndex = 2 * index + 1;
            let rightChildIndex = 2 * index + 2;
            let leftChild, rightChild;
            let swap = null;

            if (leftChildIndex < length) {
                leftChild = this.heap[leftChildIndex];
                if (!this.isCorrectOrder(element, leftChild)) {
                    swap = leftChildIndex;
                }
            }

            if (rightChildIndex < length) {
                rightChild = this.heap[rightChildIndex];
                if (
                    (swap === null && !this.isCorrectOrder(element, rightChild)) ||
                    (swap !== null && !this.isCorrectOrder(leftChild, rightChild))
                ) {
                    swap = rightChildIndex;
                }
            }

            if (swap === null) break;
            this.heap[index] = this.heap[swap];
            index = swap;
        }
        this.heap[index] = element;
    }

    peek() {
        return this.heap[0] || null;
    }
}
    

Step 2: Create a Function to Handle Commands

Now, we will create a function to process various commands based on the Absolute Value Heap. The command 'I' represents insertion, while 'D' represents deletion.


function processCommands(commands) {
    const heap = new AbsoluteValueHeap();
    const results = [];

    for (const command of commands) {
        const [action, value] = command.split(' ');
        const num = parseInt(value);

        if (action === 'I') {
            heap.insert(num);
        } else if (action === 'D' && num === 1) {
            const deleted = heap.delete();
            results.push(deleted !== null ? deleted : 0);
        } else if (action === 'D' && num === -1) {
            // No need to delete since it's implemented as a min heap
            const deleted = heap.delete();
            results.push(deleted !== null ? deleted : 0);
        }
    }
    return results;
}
    

Step 3: Summary of the Complete Code

Now we will combine all the previously created code into a complete summary.


class AbsoluteValueHeap {
    // Utilize the previously defined code.
}

function processCommands(commands) {
    // Utilize the previously defined code.
}

// Execute the test example
const commands = ["I 5", "I -5", "I 3", "I -2", "I 0", "I 4", "D 1", "D 1"];
console.log(processCommands(commands)); // [0, -2, 3, 4]
    

Conclusion

In this course, we explored the process of implementing an Absolute Value Heap in JavaScript to solve the given problem. To aid understanding of the algorithm, we explained the basic concepts of heap sorting and the operational principles of a heap based on absolute values. We hope that this course will help you develop your skills in more advanced data structures and algorithm problem-solving.

JavaScript Coding Test Course, Finding the Critical Path

Problem Description

You are managing a project management system where various tasks are interconnected. Each task must be performed for a specific duration, and certain tasks can only start after others have been completed. Implement an algorithm to determine the minimum time required for the project to be completed based on these relationships.

The project consists of the following information:

  • n : number of tasks
  • dependencies : an array representing the dependency relationships between each task
  • times : an array of the time required to perform each task

The function format is as follows:

function criticalPath(n, dependencies, times) {
    // Write your code here.
}
    

Example Input

n = 5
dependencies = [[1, 0], [2, 1], [3, 1], [3, 2], [4, 3]]
times = [3, 2, 5, 1, 2]
Input: criticalPath(n, dependencies, times)
    

Example Output

Output: 11
    

Problem Solving Approach

To solve this problem, the following steps should be taken:

1. Graph Modeling

Represent the tasks and their dependency relationships as a graph. Each task can be represented as a vertex, and the dependencies as edges.

2. Topological Sort

Determine the order of task execution through topological sorting of the given graph. Topological sorting is the process of finding a linear arrangement of all vertices in a directed graph.

3. Calculate the Longest Path

Use the topological sort to calculate the start time of each task and ultimately find the minimum time required for all tasks to be completed.

Implementation Code

Below is the JavaScript code that implements the above approach:

function criticalPath(n, dependencies, times) {
    const adjList = Array.from({length: n}, () => []);
    const inDegree = Array(n).fill(0);
    
    // 1. Build the graph and calculate in-degrees
    for (const [next, prev] of dependencies) {
        adjList[prev].push(next);
        inDegree[next]++;
    }
    
    // 2. Create a queue for topological sorting
    const queue = [];
    const timeToComplete = Array(n).fill(0);
    
    for (let i = 0; i < n; i++) {
        timeToComplete[i] = times[i];
        if (inDegree[i] === 0) {
            queue.push(i);
        }
    }
    
    // 3. Calculate the longest path
    let maxTime = 0;

    while (queue.length) {
        const current = queue.shift();
        maxTime = Math.max(maxTime, timeToComplete[current]);

        for (const neighbor of adjList[current]) {
            timeToComplete[neighbor] = Math.max(timeToComplete[neighbor], timeToComplete[current] + times[neighbor]);
            inDegree[neighbor]--;
            if (inDegree[neighbor] === 0) {
                queue.push(neighbor);
            }
        }
    }
    
    return maxTime;
}
    

Code Explanation

Now, let’s look at each part of the code:

1. Build the graph and calculate in-degrees

First, based on the dependency relationships given in the input, an adjacency list is created, and the in-degrees of each vertex are calculated. Tasks with an in-degree of 0 can start immediately, so they are added to the queue.

2. Topological sorting and longest path calculation

Tasks are removed one by one from the queue, updating the longest completion times for their subsequent tasks. If the in-degree of a subsequent task becomes 0, it is added back to the queue. After processing all tasks, the longest recorded time is the critical path.

Time Complexity Analysis

This algorithm explores each vertex and edge of the graph once, so its time complexity is O(V + E), where V is the number of tasks and E is the number of dependency relationships between tasks.

Final Thoughts

Finding the critical path is an important element in project management and scheduling, and it is widely used in industry. This problem allows you to understand the concepts of graphs and topological sorting, while also developing your ability to solve complex problems in JavaScript.

Additional Practice Problems

Now, to test your skills, try solving the following problems:

  1. Implement an algorithm to track changes in the critical path when the dependency relationships between tasks change.
  2. Consider not only the time required for tasks but also their costs. What would be your approach to finding the optimal path in this case?
  3. How can you apply topological sorting when the graph has a different structure (e.g., directed acyclic graph)?

References

If you want to know more about the critical path problem, check the links below:

JavaScript Coding Test Course, Why is Debugging Important?

Coding tests are one of the important ways to assess the capabilities of a software engineer. In particular, JavaScript is one of the most widely used languages in web development and is often used to solve various algorithmic problems. In this post, we will present an algorithm problem that can be solved with JavaScript and emphasize the importance of debugging in the process of finding a solution.

Problem Description

Problem: Two Sum

The problem is to find two numbers in a given array such that their sum equals a specific value (target), and return the indices of those two numbers. It is assumed that there is always exactly one solution.

Function Signature

function twoSum(nums: number[], target: number): number[] {
        // Your code here
    }

Example Input and Output

  • Input: nums = [2, 7, 11, 15], target = 9
  • Output: [0, 1]
  • Input: nums = [3, 2, 4], target = 6
  • Output: [1, 2]
  • Input: nums = [3, 3], target = 6
  • Output: [0, 1]

Solution

To solve this problem, we need to iterate through the array and check if the complement of each element exists in the array. However, this method has a worst-case time complexity of O(n^2), which is inefficient. Therefore, we can use a hashmap (or object) to achieve a more efficient O(n) time complexity.

Step 1: Problem Analysis

Given an array [2, 7, 11, 15] and a target of 9, we can solve it through the following steps:

  • Look at 2 and check if 7 (9 – 2) exists in the hashmap.
  • Since 7 is not there, add 2 to the hashmap.
  • Look at 7 and check if 2 (9 – 7) exists in the hashmap.
  • Since 2 exists, we return the indices [0, 1].

Step 2: Write the Code

function twoSum(nums, target) {
        const map = new Map();
        
        for (let i = 0; i < nums.length; i++) {
            const complement = target - nums[i];
            
            if (map.has(complement)) {
                return [map.get(complement), i];
            }
            map.set(nums[i], i);
        }
        
        throw new Error("No two sum solution");
    }

Step 3: Debugging Process

After writing the code, it is essential to go through a debugging process. Here are some things to pay attention to during code debugging:

  • Error handling: Ensure that appropriate error messages are returned if the input array is empty or if no two numbers can be found.
  • Variable checking: Print intermediate results to the console to ensure that the map object is functioning correctly.
  • Performance review: Especially test performance with larger input data.

Importance of Debugging

Debugging is one of the key processes in programming. Through debugging, we can identify and fix issues in the code, allowing us to develop higher-quality software. Debugging is particularly important for the following reasons:

  1. Improving problem-solving skills: The debugging process provides an opportunity to learn how to analyze and solve various problems.
  2. Improving code readability: During the process of finding and fixing issues, we learn methods to enhance code readability.
  3. Improving project quality: The process of identifying and fixing errors in advance enhances the quality of the final product.
  4. Foundation for team collaboration: Debugging experiences enhance collaboration among team members and help in understanding each other’s code.

Conclusion

In this posting, we emphasized the importance of JavaScript coding tests and the necessity of debugging through a simple algorithm problem. Not only is it crucial to solve problems, but finding and fixing errors that may occur in the process is essential for growing as a better developer. We will return with more diverse topics in the future.

javascript coding test course, salesman’s dilemma

Published on:

Author: Coding Expert

Problem Description

A salesman is visiting various cities to sell products. The salesman knows the prices of products that can be sold in each city, as well as the travel costs between cities. The salesman needs to set a goal and find the most efficient route to achieve that goal. In other words, the salesman must visit each city only once and find a route that allows him to return home while maximizing his profit.

Input

The input consists of the number of cities n, an array of sale prices prices, and a 2D array of travel costs costs. The prices are represented by prices[i] for the price of the product in the i-th city, and the travel costs are represented by costs[i][j] for the cost of traveling from city i to city j.

Output

The function should return the maximum profit that the salesman can achieve by returning home slowly.

Constraints

  • 2 ≤ n ≤ 10
  • 0 ≤ prices[i] ≤ 1000
  • 0 ≤ costs[i][j] ≤ 1000

Problem-Solving Approach

This problem is similar to the ‘Traveling Salesman Problem’ and can be solved using backtracking or dynamic programming. Essentially, the salesman needs to try all possible combinations to optimize the route that visits all cities and returns home.

Step 1: Understanding the Problem

Since the salesman must visit all cities, he needs to explore all paths between cities while considering the item sale profits and travel costs for each path. The goal is to calculate profits and costs to select the optimal route.

Step 2: Designing the Algorithm

To solve this problem, follow these steps:

  • Assume each city as the starting city and visit each city while exploring all possible paths.
  • Calculate the sale profits and travel costs for each path to update the optimal profit.
  • After visiting all cities, calculate the cost to return to the original city as well.

Step 3: Implementation

Now let’s move on to the implementation phase. We will write a function in JavaScript to find the maximum profit of the salesman.


function maxProfit(n, prices, costs) {
    let maxProfit = -Infinity;

    function backtrack(currentCity, visited, currentProfit) {
        // If all cities are visited, return home.
        if (visited.length === n) {
            const returnCost = costs[currentCity][0]; // Cost to return to the starting city
            const totalProfit = currentProfit - returnCost; // Calculate total profit
            maxProfit = Math.max(maxProfit, totalProfit);
            return;
        }

        // Visit each city.
        for (let nextCity = 0; nextCity < n; nextCity++) {
            if (!visited.includes(nextCity)) {
                visited.push(nextCity); // Record city visit
                const nextProfit = currentProfit + prices[nextCity]; // Calculate profit for next city
                const travelCost = costs[currentCity][nextCity]; // Travel cost
                backtrack(nextCity, visited, nextProfit - travelCost); // Recursive call
                visited.pop(); // Remove visit record (backtracking)
            }
        }
    }

    // Start from the 0th city
    backtrack(0, [0], prices[0]);

    return maxProfit;
}

// Example usage
const n = 4;
const prices = [100, 70, 90, 40];
const costs = [
    [0, 10, 15, 20],
    [10, 0, 35, 25],
    [15, 35, 0, 30],
    [20, 25, 30, 0]
];

console.log(maxProfit(n, prices, costs));
        

Step 4: Code Explanation

The maxProfit function defined above performs the following tasks:

  • currentCity: Tracks the current city.
  • visited: Tracks the cities visited so far.
  • currentProfit: Tracks the cumulative profit so far.

We recursively explore each city. After visiting all cities, we calculate the cost to return home to update the total profit.

Example Test

When running the code, the maxProfit function will return the maximum profit. It is advisable to experiment with various input values to observe the performance of the algorithm.

Conclusion

In this lesson, we explored the Traveling Salesman Problem. It is important to understand the theoretical background and implementation methods that frequently appear in coding tests. By exploring various routes and quantitatively calculating the optimal profit, we learned how to utilize the powerful features of JavaScript.

In the next session, we will cover another algorithm problem. If you have any questions or feedback, please leave a comment!

Javascript Coding Test Course, Calculating the Area of a Polygon

In this lecture, we will cover one of the frequently asked questions in coding tests, which is the “Calculating the Area of a Polygon” problem. We will conduct in-depth learning on how to implement an algorithm to calculate the area of a polygon using JavaScript.

Problem Description

Write a function to calculate the area of a polygon given the coordinates of its vertices. The vertices of the polygon are sorted either in a clockwise or counterclockwise direction, and the vertex coordinates are represented as integers in a two-dimensional coordinate system.

Input

  • The number of vertices of the polygon n (3 ≤ n ≤ 1000)
  • The coordinates of n vertices (x1, y1), (x2, y2), ..., (xn, yn)

Output

The area of the polygon should be printed rounded to two decimal places.

Solution Process

There are several methods to calculate the area of a polygon. Here, we will use the most common “Shoelace Formula (or Polygon Area Formula)”. This formula allows us to easily calculate the area of a polygon.

Shoelace Formula

For the given vertices (x1, y1), (x2, y2), ..., (xn, yn), the area A is calculated as follows:

A = (1/2) * | Σ (xi * yi+1 - yi * xi+1) | 

Here, i+1 is set to return to 1 when i reaches n using modular arithmetic. This formula considers the contributions of all edges of the polygon in calculating the area.

JavaScript Code Implementation

Let’s implement the above formula in code. Below is the code written in JavaScript.


function calculatePolygonArea(vertices) {
    let n = vertices.length;
    let area = 0;

    for (let i = 0; i < n; i++) {
        let x1 = vertices[i][0];
        let y1 = vertices[i][1];
        let x2 = vertices[(i + 1) % n][0];
        let y2 = vertices[(i + 1) % n][1];

        area += (x1 * y2) - (y1 * x2);
    }

    return Math.abs(area / 2).toFixed(2);
}

// Example
let vertices = [[0, 0], [4, 0], [4, 3], [0, 4]];
console.log(calculatePolygonArea(vertices)); // 12.00

Code Explanation

  • The calculatePolygonArea function takes an array of vertex coordinates vertices as input.
  • It calculates the number of polygon vertices n.
  • It initializes the area area to 0, then calculates the area for all vertices.
  • It adds the contribution to the area using the current vertex (xi, yi) and the next vertex (xi+1, yi+1).
  • It connects the last vertex with the first vertex through modulus operation to complete the area calculation.
  • It returns the calculated area rounded to two decimal places.

Test Cases

If you have checked the code, let’s add the following test cases.


let testVertices1 = [[0, 0], [0, 2], [2, 2], [2, 0]]; // Rectangle
let testVertices2 = [[0, 0], [4, 0], [4, 3], [0, 4]]; // Irregular Polygon

console.log(calculatePolygonArea(testVertices1)); // 4.00
console.log(calculatePolygonArea(testVertices2)); // 12.00

Conclusion

In this lecture, we explored the theory behind calculating the area of a polygon along with an example of its implementation in JavaScript. I believe that understanding the formula to calculate the area of a polygon and implementing it in actual code will help in coding tests.

The problem of calculating the area of a polygon is frequently asked in real coding tests, so be sure to firmly grasp the basic theories and problem-solving processes. In the next lecture, we will cover another algorithm problem, so I hope for your continued interest.