C++ Coding Test Course, Finding Cities at a Specific Distance

Hello, in this lecture, we will explore the process of solving algorithm problems using C++. We will tackle a problem called ‘Finding Cities at a Certain Distance’ through graph traversal. This problem is one of the frequent topics in coding tests and can be solved using graph traversal techniques like BFS (Breadth-First Search) or DFS (Depth-First Search).

Problem Description

The requirements of the problem are as follows:

There are N cities. Each city is connected to one another, and there are roads. The road connecting two cities A and B may be one-way. Given the number of cities N, the number of roads M, and the distance K, write a program that outputs the numbers of cities that are at distance K. If there are no such cities, print -1. There are no multiple roads connecting two cities A and B, and all roads are one-way.

Input Format

The first line contains the number of cities N, the number of roads M, and the distance K. From the second line onward, M lines contain information about the roads, with each road represented by two integers A and B indicating that there is a one-way road from A to B.

Output Format

You should output the number of the city at distance K, and if there are no such cities, you should output -1.

Example Input

    4 4 2
    1 2
    1 3
    2 3
    2 4
    

Example Output

    4
    

Problem-Solving Process

We will solve the problem using a graph. We’ll treat each city as a vertex and the roads as edges to build the graph. Let’s go through the steps to solve the problem.

1. Representing the Graph

In the first step, we will represent the graph using an adjacency list. The adjacency list is a format that stores a list of connected vertices for each vertex. This allows us to easily verify the connection relationships between cities.

2. Searching Using BFS

We will use the BFS (Breadth-First Search) algorithm to search for cities at a certain distance. BFS is an algorithm that starts from one vertex and explores all its adjacent vertices level by level, which is suitable for finding nodes at a specific distance.

3. Storing and Handling Results

We will collect all the city numbers that correspond to distance K and output them. Additionally, we will set it to output -1 if there are no cities at distance K.

Implementation Code

Below is the code implemented in C++. This code allows us to solve the problem.

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

int main() {
    int N, M, K;
    cin >> N >> M >> K;

    vector<vector<int>> graph(N + 1);
    for (int i = 0; i < M; i++) {
        int A, B;
        cin >> A >> B;
        graph[A].push_back(B);
    }

    vector<int> distance(N + 1, -1);
    queue<int> q;

    // Set the starting city 1 to distance 0
    distance[1] = 0;
    q.push(1);

    while (!q.empty()) {
        int current = q.front();
        q.pop();

        for (int next : graph[current]) {
            if (distance[next] == -1) {
                distance[next] = distance[current] + 1;
                q.push(next);
            }
        }
    }

    vector<int> result;
    for (int i = 1; i <= N; i++) {
        if (distance[i] == K) {
            result.push_back(i);
        }
    }

    if (result.empty()) {
        cout << -1 << endl;
    } else {
        sort(result.begin(), result.end());
        for (int city : result) {
            cout << city << endl;
        }
    }

    return 0;
}
    

Conclusion

In this lecture, we learned about graph traversal techniques through the problem of ‘Finding Cities at a Certain Distance’ and explored how to solve the problem using BFS. Problems like this are frequently encountered in algorithm interviews, so it’s important to practice enough to develop problem-solving skills. In the next lecture, we will tackle another algorithm problem. Thank you!

C++ Coding Test Course, Finding the Diameter of a Tree

1. Problem Definition

This is the problem of finding the diameter of a given tree. The diameter of a tree refers to the length of the longest path between any two nodes. A tree is a graph composed of nodes and edges, characterized as a connected graph without cycles. To solve this problem, we can use either the DFS (Depth First Search) or BFS (Breadth First Search) algorithm.

2. Problem Input

The first line contains the number of nodes N. From the next line onwards, N-1 lines contain the edges, with each edge represented by two integers a and b. This indicates that there is an edge between node a and node b.

Input Example

    5
    1 2
    1 3
    2 4
    2 5
    

3. Problem Output

The program outputs the diameter of the tree.

Output Example

    3
    

4. Algorithm Explanation

The algorithm for finding the diameter of a tree proceeds as follows:

  1. Store the tree in the form of an adjacency list.
  2. Use the first DFS to find the farthest node.
  3. Use a second DFS to find the farthest node from the one identified in the first DFS.
  4. The result of the second DFS is the diameter of the tree.

5. C++ Code Implementation

The C++ code to calculate the diameter of the tree is as follows:


#include 
#include 
#include 

using namespace std;

vector > tree;
vector visited;

int furthestNode(int node) {
    visited[node] = true;

    int maxDistance = 0;
    int farthestNode = node;

    for (int neighbor : tree[node]) {
        if (!visited[neighbor]) {
            int distance = furthestNode(neighbor) + 1;
            if (distance > maxDistance) {
                maxDistance = distance;
                farthestNode = neighbor;
            }
        }
    }

    return farthestNode;
}

int main() {
    int N;
    cin >> N;

    tree.resize(N + 1);
    visited.resize(N + 1, false);

    for (int i = 0; i < N - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    int farthestFromStart = furthestNode(1);
    
    fill(visited.begin(), visited.end(), false);
    
    int diameterEnd = furthestNode(farthestFromStart);
    
    cout << diameterEnd << endl;

    return 0;
}
    

6. Code Explanation

The code is structured as follows:

  1. Include libraries and declare global variables.
  2. Perform DFS through the furthestNode function to return the farthest node.
  3. In the main function, read input and construct the tree.
  4. Use the first DFS to find the farthest node from the starting node.
  5. Use the second DFS to find the farthest node from the node found in the first DFS, which will be the end of the diameter.
  6. Output the length of the diameter.

7. Time Complexity Analysis

The time complexity of this algorithm is O(N). It executes DFS for each node while visiting N nodes, thus it is proportional to the size of the tree.

8. Conclusion

In this lesson, we learned how to solve the problem of finding the diameter of a tree. It is important to understand the process of exploring the tree’s structure and deriving a solution using DFS. By practicing various graph problems, you can enhance your algorithm skills.

9. References

C++ Coding Test Course, Understanding Trees

Author: Blog Author | Date: October 1, 2023


1. Problem Description

In this tutorial, we will learn about the tree data structure, which frequently appears in C++ coding tests, and solve algorithm problems using it.

The given problem is as follows:

Problem: Finding the Minimum and Maximum in a Binary Search Tree

Problem Description: Given a binary search tree (BST), write a program to find and output the minimum and maximum values of that tree.

A binary search tree has the following properties:

  • All nodes in the left subtree are less than the current node.
  • All nodes in the right subtree are greater than the current node.

The tree provided as input is in the following format:

            5
           / \
          3   7
         / \   \
        1   4   8
            

The output should print the minimum and maximum values in order, and in the above example, the output would be:

            1
            8
            

Understanding binary search trees is essential to solve problems like this.

2. Insights into Binary Search Trees (BST)

A binary search tree (BST) is a data structure where each node contains data, and there are two child nodes (left and right). The left child node is always less than its parent node, and the right child node is always greater than its parent node.

Thanks to this property, using binary search trees allows for data searching, insertion, and deletion in O(log n) time.

  • The minimum value is found at the leftmost node of the left subtree.
  • The maximum value is found at the rightmost node of the right subtree.

When the depth of the tree is n, it is very efficient because you only need to traverse the tree to find the minimum and maximum values.

3. Algorithm Design

To find the minimum and maximum, we can define two functions. Each function explores the tree using either a recursive or iterative method.

            // Node structure definition for binary search tree
            struct Node {
                int data;
                Node* left;
                Node* right;
                
                Node(int val) : data(val), left(nullptr), right(nullptr) {}
            };

            // Function to find the minimum value
            Node* findMin(Node* root) {
                if (root == nullptr) return nullptr;
                if (root->left == nullptr) return root;
                return findMin(root->left);
            }

            // Function to find the maximum value
            Node* findMax(Node* root) {
                if (root == nullptr) return nullptr;
                if (root->right == nullptr) return root;
                return findMax(root->right);
            }
            

Each function provides a method to find the minimum or maximum by continuously moving left or right from the provided node.

4. C++ Code Implementation

The complete code for obtaining the minimum and maximum values in a binary search tree is as follows:

            #include 
            using namespace std;

            // Node structure definition for binary search tree
            struct Node {
                int data;
                Node* left;
                Node* right;

                Node(int val) : data(val), left(nullptr), right(nullptr) {}
            };

            // Function to find the minimum value
            Node* findMin(Node* root) {
                if (root == nullptr) return nullptr;
                if (root->left == nullptr) return root;
                return findMin(root->left);
            }

            // Function to find the maximum value
            Node* findMax(Node* root) {
                if (root == nullptr) return nullptr;
                if (root->right == nullptr) return root;
                return findMax(root->right);
            }

            int main() {
                // Sample tree creation
                Node* root = new Node(5);
                root->left = new Node(3);
                root->right = new Node(7);
                root->left->left = new Node(1);
                root->left->right = new Node(4);
                root->right->right = new Node(8);
                
                Node* minNode = findMin(root);
                Node* maxNode = findMax(root);

                if (minNode) cout << "Minimum value: " << minNode->data << endl;
                if (maxNode) cout << "Maximum value: " << maxNode->data << endl;

                // Freeing dynamically allocated memory
                delete root->left->left;
                delete root->left->right;
                delete root->left;
                delete root->right->right;
                delete root->right;
                delete root;

                return 0;
            }
            

Running the above code will output the minimum and maximum values of the given binary search tree.

5. Code Explanation

Let’s explain the main components of the program:

  • Node Structure: This structure defines each node that comprises the binary search tree. Each node contains an integer data and two child pointers (left and right).
  • findMin Function: This function follows the left child nodes from the given node to find the minimum value node. If there is no left child, that node is the minimum value.
  • findMax Function: This function follows the right child nodes from the given node to find the maximum value node. If there is no right child, that node is the maximum value.
  • main Function: This function creates a sample tree, calls each function to find the minimum and maximum values, prints the results, and then frees the dynamically allocated memory.

6. Conclusion

In this tutorial, we examined an algorithm to find the minimum and maximum values using C++’s binary search tree. The binary search tree is a highly efficient data structure with various applications. A deeper understanding of tree structures will aid in solving other tree-related problems.

In future lessons, we will prepare for more complex tree problems and algorithms that utilize trees. Stay tuned for the next tutorial!

Thank you for visiting the blog. If you have any questions or need further discussion, please leave a comment.


© 2023 Blog Author. All rights reserved.

C++ Coding Test Course, Finding the Parent of a Tree

Problem Description

Write a program to find the parent node of each node in a given binary tree. The nodes of the binary tree are labeled with numbers and are defined with the following structure:


    struct Node {
        int value;
        Node* left;
        Node* right;
        
        Node(int val) : value(val), left(nullptr), right(nullptr) {}
    };
    

You will be given a list of nodes as input and the value of the requested node. The output should be the value of the parent node of the specified node, and if there is no parent node, it should return -1.

Example


    Input:
    5
    2 1
    7 3
    6 4
    0 -1
    Requested Node: 3
    
    Output:
    7
    

Approach to the Problem

To solve the problem, we will traverse the binary tree to identify the parent of each node. We will follow these steps:

  1. Set the root node of the tree as the starting point.
  2. Use a queue to explore the nodes of the tree in a BFS (Breadth-First Search) manner.
  3. Check each node and store the parent of the requested node.
  4. Once the requested node is found, print the corresponding parent node.

Code Implementation


    #include 
    #include 
    #include 
    using namespace std;

    struct Node {
        int value;
        Node* left;
        Node* right;

        Node(int val) : value(val), left(nullptr), right(nullptr) {}
    };

    Node* buildTree(const vector>& nodes) {
        unordered_map nodeMap;
        for (const auto& p : nodes) {
            int child = p.first;
            int parent = p.second;

            if (nodeMap.find(child) == nodeMap.end())
                nodeMap[child] = new Node(child);

            if (parent != -1) {
                if (nodeMap.find(parent) == nodeMap.end())
                    nodeMap[parent] = new Node(parent);

                if (!nodeMap[parent]->left)
                    nodeMap[parent]->left = nodeMap[child];
                else
                    nodeMap[parent]->right = nodeMap[child];
            }
        }
        return nodeMap.begin()->second; // Return the first added node as root
    }

    int findParent(Node* root, int target) {
        if (!root) return -1;

        queue q;
        q.push(root);
        Node* parent = nullptr;

        while (!q.empty()) {
            int size = q.size();
            for (int i = 0; i < size; ++i) {
                Node* current = q.front();
                q.pop();
                
                if (current->left) {
                    if (current->left->value == target) {
                        return current->value;
                    }
                    q.push(current->left);
                }

                if (current->right) {
                    if (current->right->value == target) {
                        return current->value;
                    }
                    q.push(current->right);
                }
            }
        }
        
        return -1; // If parent is not found
    }

    int main() {
        // Tree structure: (child, parent)
        vector> nodes = {{2, 1}, {7, 3}, {6, 4}, {5, -1}};

        Node* root = buildTree(nodes); // Build tree
        int target = 3; // Requested node
        int parentValue = findParent(root, target);

        cout << parentValue << endl; // Print parent

        return 0;
    }
    

Code Explanation

The code above illustrates the overall process of constructing a binary tree and finding the parent of the requested node.

  • buildTree: This function constructs the tree using the provided (child, parent) list. It stores each node in a map to establish the parent-child relationship.
  • findParent: This function uses BFS to find the parent of the requested value. It traverses each node and returns the value of the current node if the child node equals the requested value.
  • main: The entry point of the program, which builds the tree using sample input and finds the parent.

Conclusion

This problem is beneficial for understanding the basic structure of a binary tree and learning how to effectively utilize data structures. The process of exploring nodes and finding parent nodes through BFS is a widely used approach in real-world applications. Care should be taken at each step, and addressing exceptions can help prevent issues.

Additional Problems

As a variation of this problem, consider finding the child nodes of a specific node or calculating the depth of the tree. Various variation problems can deepen your understanding of trees.

Tips and Cautions

  • When using recursion, be careful not to fall into an infinite loop.
  • Clearly define conditions to handle cases where a node has no parent.
  • The performance of the algorithm may vary based on the structure of the tree, so experiment with various cases.

C++ Coding Test Course, Traversing Trees

Problem Description

This problem involves implementing a tree traversal algorithm to output the values of the nodes that make up a binary tree in a specific order. A binary tree is a data structure where each node can have up to two child nodes, and there are various ways to visit the nodes. The most common tree traversal methods are preorder traversal, inorder traversal, and postorder traversal.

Definition of a Tree

    struct TreeNode {
        int val; // Value of the node
        TreeNode *left; // Left child node
        TreeNode *right; // Right child node
        TreeNode(int x) : val(x), left(NULL), right(NULL) {} // Constructor
    };

Approach

The approaches for each traversal are as follows:

  • Preorder Traversal: Visit the current node, then visit the left child node, and finally visit the right child node.
  • Inorder Traversal: Visit the left child node, then visit the current node, and finally visit the right child node.
  • Postorder Traversal: Visit the left child node, then the right child node, and lastly visit the current node.

Preorder Traversal Code Implementation

Below is the code implemented in C++ for preorder traversal:

 
#include <iostream>
#include <vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

void preorderTraversal(TreeNode* root, vector& result) {
    if (root == nullptr) return; // Base case
    result.push_back(root->val); // Visit node
    preorderTraversal(root->left, result); // Visit left subtree
    preorderTraversal(root->right, result); // Visit right subtree
}

int main() {
    // Initialize binary tree
    TreeNode* 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);
    
    vector result;
    preorderTraversal(root, result);
    
    // Print results
    for (int val : result) {
        cout << val << " ";
    }
    return 0;
}
    

Inorder Traversal Code Implementation

Below is the code implemented in C++ for inorder traversal:


void inorderTraversal(TreeNode* root, vector& result) {
    if (root == nullptr) return; // Base case
    inorderTraversal(root->left, result); // Visit left subtree
    result.push_back(root->val); // Visit node
    inorderTraversal(root->right, result); // Visit right subtree
}

// The main function remains the same
    

Postorder Traversal Code Implementation

Below is the code implemented in C++ for postorder traversal:


void postorderTraversal(TreeNode* root, vector& result) {
    if (root == nullptr) return; // Base case
    postorderTraversal(root->left, result); // Visit left subtree
    postorderTraversal(root->right, result); // Visit right subtree
    result.push_back(root->val); // Visit node
}

// The main function remains the same
    

Time Complexity and Optimization

The time complexity of each tree traversal method is O(n). This is because each node in the tree is visited once. The space complexity can be O(h) in the worst case (where h is the height of the tree) if the tree is not complete. If the tree is linear in shape, O(n) space may be needed.

Optimization Methods

To further improve the performance of tree traversal, it can be implemented iteratively using a stack. For example, when implementing preorder traversal iteratively, it can be done as follows:


#include <stack>

void iterativePreorderTraversal(TreeNode* root, vector& result) {
    if (root == nullptr) return; 
    stack stk;
    stk.push(root);

    while (!stk.empty()) {
        TreeNode* node = stk.top();
        stk.pop();
        result.push_back(node->val); // Visit node
        if (node->right != nullptr) stk.push(node->right); // Add right child first
        if (node->left != nullptr) stk.push(node->left); // Add left child
    }
}

// The main function remains the same
    

Conclusion

In this tutorial, we explained how to implement various traversal algorithms for a binary tree using C++. It is important to understand the methodologies for each traversal and to write specific code to practice this. Binary trees play a crucial role in various fields, so it is advisable to master them.

References