C++ Coding Test Course, Finding the Longest Increasing Subsequence

Problem Description

You need to find the Longest Increasing Subsequence (LIS) in a given integer array.
A subsequence is made up of selected elements while maintaining the order of elements from the original array.
This problem can be solved using Dynamic Programming or Binary Search.

Example

Input: [10, 9, 2, 5, 3, 7, 101, 18]
Output: 4 (Longest Increasing Subsequence: [2, 3, 7, 101])

Approach to the Problem

There are several ways to solve this problem. The most representative method is using dynamic programming,
which has a time complexity of O(N^2). However, this problem can also be solved using binary search with a time complexity of O(N log N).

1. Dynamic Programming Approach

The key to the dynamic programming approach is to store ‘intermediate results’ to avoid repeating the same calculations.
It proceeds in the following steps.

  1. Initialize a dp array to store the lengths. dp[i] stores the maximum length of the increasing subsequence ending with the element at index i.
  2. Use a nested loop to compare each element and update the dp array.
    If the i-th element is greater than the j-th element, set dp[i] to max(dp[i], dp[j] + 1).
  3. Finally, find the maximum value in the dp array and output it.

Code (Dynamic Programming)


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int lengthOfLIS(vector& nums) {
    if (nums.empty()) return 0;
    vector dp(nums.size(), 1);
    
    for (int i = 1; i < nums.size(); i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
    }
    return *max_element(dp.begin(), dp.end());
}

int main() {
    vector nums = {10, 9, 2, 5, 3, 7, 101, 18};
    cout << "Length of the longest increasing subsequence: " << lengthOfLIS(nums) << endl;
    return 0;
}
    

2. Binary Search Approach

This method is more efficient, having a time complexity of O(N log N). The basic idea of this method is
to maintain an array that contains ‘the last elements of the subsequences’.

  1. Initialize an array to store the results.
  2. For each element, perform the following:
    • If the current number is greater than the last element of the result array, add it to the array.
    • Otherwise, use binary search to find the position where the current number should go and replace it.
  3. The size of the result array will be the length of the longest increasing subsequence.

Code (Binary Search)


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int lengthOfLIS(vector& nums) {
    vector tails;
    
    for (int num : nums) {
        auto it = lower_bound(tails.begin(), tails.end(), num);
        if (it == tails.end()) {
            tails.push_back(num);
        } else {
            *it = num;
        }
    }
    return tails.size();
}

int main() {
    vector nums = {10, 9, 2, 5, 3, 7, 101, 18};
    cout << "Length of the longest increasing subsequence: " << lengthOfLIS(nums) << endl;
    return 0;
}
    

Conclusion

The problem of “Finding the Longest Increasing Subsequence” is an algorithm problem that can be solved in various ways.
Understanding and implementing approaches to solve problems is very helpful in coding tests.
Through this problem, learn the basics of dynamic programming and binary search, and develop the ability to apply them to various algorithm problems.

Additional Learning Resources

C++ Coding Test Course, ‘Finding Good Numbers’

1. Introduction

Solving algorithm problems is a very important factor in job preparation. Today, we will take a deep dive into the ‘good number’ problem that many developers face. A ‘good number’ refers to a number that meets certain conditions, and through this problem, you can enhance your thinking skills and improve your abilities.

2. Problem Description

You will receive an integer n as input. Among all the numbers less than or equal to n, we define a ‘good number’ as a number whose sum of digits is less than or equal to 10.
For example, 23 is a good number, but 24 is not because 2+4=6. The task is to find all ‘good numbers’ less than or equal to the given n.

2.1. Input

An integer n (1 <= n <= 10000)

2.2. Output

Outputs the ‘good numbers’ less than or equal to n.

3. Approach

To solve this problem, you can use the following approach.

  • Iterate through numbers from 1 to n and separate each digit of the number.
  • Calculate the sum of the digits.
  • If the sum of the digits is less than or equal to 10, consider it a ‘good number’ and print it.

3.1. Calculating the Sum of Digits

To calculate the sum of the digits, you can use the method of dividing the number by 10 and obtaining the remainder. For example, when dividing 23, you get 3 (23 % 10) and 2 (23 / 10).
By following this process, you can identify the digits of each number and calculate the sum.

4. Code Implementation

Now, let’s write C++ code based on the above approach. Below is the code for the program that finds ‘good numbers’.


#include 
using namespace std;

bool isGoodNumber(int number) {
    int sum = 0;
    while (number > 0) {
        sum += number % 10; // Adding the current digit
        number /= 10; // Calculating the next digit
    }
    return sum <= 10; // Check if the sum of digits is less than or equal to 10
}

int main() {
    int n;
    cout << "Please enter an integer: ";
    cin >> n;

    cout << "Good numbers: ";
    for (int i = 1; i <= n; i++) {
        if (isGoodNumber(i)) {
            cout << i << " "; // Print good number
        }
    }
    cout << endl;
    return 0;
}
    

5. Code Explanation

The above code is a simple program, consisting of the isGoodNumber function to determine ‘good numbers’ and the main function main.

5.1. isGoodNumber Function

This function takes an integer as input, calculates the sum of its digits, and returns true or false based on the result.
It uses a loop to separate and sum each digit of the number.

5.2. Main Function

In the main function, the user inputs n, and it checks numbers from 1 to n. It calls isGoodNumber for each number to determine if it is a good number.

6. Time Complexity

The time complexity of this algorithm is O(d * n), where d is the number of digits in n.
Summing the digits for each number is performed at most 4 times, allowing it to handle larger n values in real-time.

7. Optimization

The current code may be inefficient as it considers all numbers and calculates the sum of digits.
Instead, you can pre-calculate and store the sum of digits to reduce redundant calculations. Here is an example of code improvement for this.


#include 
#include 
using namespace std;

vector precomputeGoodNumbers(int maxNum) {
    vector goodNumbers;
    for (int i = 1; i <= maxNum; i++) {
        if (isGoodNumber(i)) {
            goodNumbers.push_back(i); // Store good numbers
        }
    }
    return goodNumbers;
}

int main() {
    int n;
    cout << "Please enter an integer: ";
    cin >> n;

    vector goodNumbers = precomputeGoodNumbers(n);
    cout << "Good numbers: ";
    for (int number : goodNumbers) {
        cout << number << " "; // Print pre-stored good numbers
    }
    cout << endl;
    return 0;
}
    

8. Conclusion

Today, we explored the problem of finding ‘good numbers’. This problem is a great example of developing basic algorithmic thinking and improving problem-solving skills.
Continue to strengthen your algorithmic skills through various problems. Happy coding!

C++ Coding Test Course, Finding the Kth Shortest Path

Hello! In this course, we will talk about one of the algorithm problems, “Finding the K-th Shortest Path”. This problem frequently appears in real coding tests and requires a deep understanding of graphs and shortest path algorithms. Through this problem, we will learn how to utilize Dijkstra’s algorithm and priority queues.

Problem Description

The problem is to find the length of the K-th shortest path between two vertices A and B in the given graph. The shortest path refers to the path with the shortest length among multiple paths, and the K-th shortest path refers to the shortest path that is longer than the shortest path. The same path can exist multiple times.

Input

  • Number of vertices N (1 ≤ N ≤ 400)
  • Number of edges M (1 ≤ M ≤ 100,000)
  • K (1 ≤ K ≤ 3000)
  • Information of each edge (starting vertex, ending vertex, weight)
  • Vertices A and B

Output

Print the length of the K-th shortest path. If the K-th shortest path does not exist, print -1.

Problem Approach

To solve this problem, we need to think about how to find the K-th shortest path. The standard Dijkstra’s algorithm is effective for finding the shortest path, but additional measures are needed to find the K-th path.

The data structures used to solve this problem are as follows:

  • Priority Queue: Ensures that the smallest value is handled first.
  • Vector: Used to represent the graph structure.

Algorithm Steps

  1. Input the graph and represent it as an adjacent list.
  2. Store the path list for each vertex in a priority queue.
  3. Use Dijkstra’s algorithm from the starting vertex to find the K-th shortest path.
  4. Find and print the K-th smallest value from the path list.

C++ Code Implementation


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

using namespace std;

const int INF = 1e9;  // Definition of infinity

struct Edge {
    int to, weight;
};

int N, M, K;
vector<Edge> graph[401];
vector<int> dist[401];  // Length of K-th shortest path for each vertex

void findKthShortestPath(int start, int end) {
    priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;
    pq.push(make_tuple(0, start, 0));  // (length, current vertex, number of paths)
    
    while (!pq.empty()) {
        auto [length, current, count] = pq.top();
        pq.pop();
        
        if (count >= K) continue; // No need to explore more than K paths
        dist[current].push_back(length);
        
        for (auto &edge : graph[current]) {
            int next = edge.to;
            int weight = edge.weight;
            pq.push(make_tuple(length + weight, next, count + 1));
        }
    }
}

int main() {
    cin >> N >> M >> K; // Getting vertices, edges, and K
    for (int i = 0; i < M; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        graph[u].push_back({v, w}); // Adding edge
    }

    int A, B;
    cin >> A >> B;

    findKthShortestPath(A, B);
    
    if (dist[B].size() < K) {
        cout << -1 << endl; // Return -1 if K-th shortest path does not exist
    } else {
        cout << dist[B][K-1] << endl; // Print K-th shortest path
    }

    return 0;
}

Conclusion

In this course, we have explored various methods of graph traversal and efficient pathfinding using priority queues through the problem of finding the K-th shortest path. In the process of solving this problem, we learned how to find the K-th path by modifying Dijkstra’s algorithm. Understanding such problems will provide opportunities to achieve high scores in actual coding tests.

I hope you also improve your understanding of graph traversal and elevate your coding skills by practicing this problem. Thank you!

C++ Coding Test Course, Finding the K-th Number

Hello! In this post, we will discuss one of the coding test problems using C++, titled “Finding the Kth Smallest Number.” The problem of finding the Kth smallest number is something that one might ponder in real life, such as when picking up coupons, and it is frequently encountered in algorithm problem-solving. In this post, I will explain in detail from the problem definition to the solution process and the C++ code implementation.

Problem Definition

Given an array of integers, the problem is to find the Kth smallest number in this array. Here, K is an integer starting from 1, representing the first smallest number, the second smallest number, and so on.

For example, let’s consider the following input:

Array: [3, 1, 2, 4, 5]
K: 3

The 3rd smallest number in the given array is 3. Therefore, the answer in this case is 3.

Input Format

  • First line: Length of the array N (1 <= N <= 100,000)
  • Second line: Integer array A (1 <= A[i] <= 10^9)
  • Third line: Integer K (1 <= K <= N)

Output Format

Print the Kth smallest number.

Problem Solving Plan

We can think of several algorithms that could be used to solve this problem.

  • Sorting: Sort the array and return the Kth index.
  • Using a Min-Heap: A heap can be used to find the Kth smallest number.
  • Quick Select Algorithm: An algorithm that can find the Kth number with an average time complexity of O(N).

Here, we will implement the solution using the most intuitive and easy-to-understand method of sorting the array.

Implementation

First, we will receive the array input using C++’s vector, and sort it using the sort() function. After that, we will structure the program to print the Kth number.

Code Implementation

#include 
#include 
#include  // include for sort()
using namespace std;

int main() {
    int N, K;
    
    // Input the size of the array N
    cout << "Please enter the length of the array N: ";
    cin >> N;

    vector A(N);
    
    // Input for array A
    cout << "Please enter the integer array A (separated by spaces): ";
    for(int i = 0; i < N; i++) {
        cin >> A[i];
    }
    
    // Input K value
    cout << "Please enter the K value: ";
    cin >> K;

    // Sort the array
    sort(A.begin(), A.end());

    // Print the Kth number (index K-1)
    cout << K << "th number is: " << A[K - 1] << endl;

    return 0;
}

Code Explanation

The code operates in the following flow:

  1. It prompts the user to input the length of the array N.
  2. A vector A of integer type with length N is created.
  3. The elements of array A are input by the user.
  4. The K value is input.
  5. Array A is sorted in ascending order.
  6. The Kth number is printed. In C++, since indexing starts from 0, K-1 must be used.

Code Analysis

The input for the problem can go up to 100,000 elements, and in the worst case, a sorting operation with a time complexity of O(N log N) will be performed. After sorting, the Kth number can be found in O(1) time, so the overall time complexity is O(N log N). This complexity is sufficient to be solved within the given time limits.

Test Cases

Let’s consider some test cases to verify the above code:

Test Case 1

Input:
5
3 1 2 4 5
3
Output:
The 3rd number is: 3

Test Case 2

Input:
10
10 9 8 7 6 5 4 3 2 1
5
Output:
The 5th number is: 5

Test Case 3

Input:
5
1 10000 500 999 1000
2
Output:
The 2nd number is: 500

Results and Optimization

We have implemented a basic solution. The method using sorting is intuitive and easy to implement; however, other methods may be required if the size of the array is very large. For example, we could use the Quick Select algorithm. The Quick Select algorithm has an average performance of O(N) and can be useful when memory is constrained.

Overview of Quick Select Algorithm

The Quick Select algorithm is a type of divide-and-conquer algorithm that selects a random pivot to divide the array into two parts, determining which part contains the Kth number and continues searching only in that part. It can quickly find the Kth number even in very large arrays.

Example of Quick Select Implementation

#include <iostream>
#include <vector>
#include <cstdlib> // for rand() function
using namespace std;

// Quick Select algorithm that finds the Kth number in the array
int quickSelect(vector& A, int left, int right, int K) {
    if (left == right) return A[left];

    int pivotIndex = left + rand() % (right - left);
    int pivotValue = A[pivotIndex];

    // Move pivot to the end of the array
    swap(A[pivotIndex], A[right]);
    int storeIndex = left;

    for (int i = left; i < right; i++) {
        if (A[i] < pivotValue) {
            swap(A[storeIndex], A[i]);
            storeIndex++;
        }
    }

    // Move pivot to its final place
    swap(A[storeIndex], A[right]);

    if (K == storeIndex) return A[K];
    else if (K < storeIndex) return quickSelect(A, left, storeIndex - 1, K);
    else return quickSelect(A, storeIndex + 1, right, K);
}

int main() {
    int N, K;

    cout << "Please enter the length of the array N: ";
    cin >> N;

    vector A(N);
    
    cout << "Please enter the integer array A (separated by spaces): ";
    for(int i = 0; i < N; i++) {
        cin >> A[i];
    }

    cout << "Please enter the K value: ";
    cin >> K;

    int result = quickSelect(A, 0, N - 1, K - 1);
    cout << K << "th number is: " << result << endl;

    return 0;
}

Conclusion

Today, we explored how to solve the problem of finding the Kth number using C++. We implemented a basic method of sorting the array to find the Kth number, and briefly discussed the more efficient Quick Select algorithm. Problems like these often appear in coding tests, so it is beneficial to learn them repeatedly. I hope this helps you to develop a basic understanding of algorithms and improve your C++ programming skills.

Thank you for reading! If you have any additional questions, please leave a comment.

C++ Coding Test Course, DNA Password

1. Problem Definition

Recently, a biologist found themselves in a situation where they needed to create a specific password using DNA sequences. This DNA sequence consists of four types of nucleotides: A, C, G, and T. The password is generated from a combination of A, C, G, and T selected from the given DNA sequence.

Problem Description

Write a program to find the substring of length K among all substrings of the given string S where the frequency of each nucleotide is minimized, and determine the number of valid passwords among them. If all frequencies are the same, select the smallest lexicographic string among those with the minimum frequency. The length of the substring must be less than or equal to the given string.

Input

  • The first line contains the length N of the DNA string S (1 ≤ N ≤ 100,000).
  • The second line contains the integer K (1 ≤ K ≤ N).
  • The third line contains the string S.

Output

Print the number of valid passwords and the smallest lexicographic password among them.

2. Algorithm Analysis

To solve this problem, we will use the Sliding Window technique. This method allows us to move through substrings of a specific length K while counting the frequency of each character (A, C, G, T) to solve the problem.

Procedure

  1. Select the first substring of length K and count the frequency of characters.
  2. Then, adjust the frequency as the sliding window moves, adding the new character and removing the old one.
  3. Check the frequency of nucleotides in each K-length substring and find the smallest frequency.
  4. After inspecting all substrings, print the count of valid passwords and the smallest lexicographic password.

3. C++ Coding Example

Below is a C++ code example to solve this problem:

        #include <iostream>
        #include <string>
        #include <unordered_map>
        #include <vector>
        #include <algorithm>

        using namespace std;

        int main() {
            int N, K;
            string S;
            cin >> N >> K;
            cin >> S;

            unordered_map freq;
            for (int i = 0; i < K; i++) {
                freq[S[i]]++;
            }

            // Initial result setup
            int min_freq = INT_MAX;
            vector passwords;

            // Sliding window to check all K-length substrings
            for (int i = 0; i <= N - K; i++) {
                if (i > 0) {
                    freq[S[i - 1]]--;
                    freq[S[i + K - 1]]++;
                }

                // Finding the smallest frequency
                int cur_min = INT_MAX;
                for (const auto &entry : freq) {
                    cur_min = min(cur_min, entry.second);
                }

                // Adding valid password
                if (cur_min <= (K / 4)) { // Frequency of all nucleotides is less than or equal to K/4
                    passwords.push_back(S.substr(i, K));
                }
            }

            // Finding the smallest lexicographic password
            sort(passwords.begin(), passwords.end());
            string smallest_password = (passwords.empty() ? "" : passwords[0]);
            
            // Output result
            cout << passwords.size() << " " << smallest_password << endl;

            return 0;
        }
    

4. Code Explanation

The above code works as follows:

  • First, the DNA string and its length, along with the length K of the substring, are input.
  • An unordered_map is used to count the frequency of the first K characters.
  • The Sliding Window technique is used to move through each K-length substring, modifying the frequency.
  • The minimum frequency is calculated for each substring, adding to the valid password list only when conditions are met.
  • Finally, the valid password list is sorted to find the smallest lexicographic password.

5. Test Cases

Now, let’s look at some test cases to solve this problem:

Test Case 1

        Input:
        8 4
        ACGTTGCA

        Output:
        1 ACGT
    

Test Case 2

        Input:
        12 5
        ACGTTACGATC

        Output:
        3 ACCTA ACTAC TCGAT
    

Test Case 3

        Input:
        6 3
        ACGTAC

        Output:
        2 ACG ACT
    

6. Conclusion

In this tutorial, we learned an algorithm utilizing the sliding window technique through the DNA password problem. This technique can be very useful in solving string-related problems and can be applied to various modified problems. It serves as a solid foundation that can be beneficial in coding tests.