Python Coding Test Course, ‘Finding Good Numbers’

Author: [Author Name] | Date: [Date]

Problem Description

This is a problem to find a number n that is a “good number.”
The conditions for a “good number” are as follows.

  • A “good number” is a natural number with two or more digits.
  • A number is defined as a “good number” if the sum of its digits is odd.
  • Find all “good numbers” less than or equal to the given n and return them as a list.

For example, if n is 30, the “good numbers” are 11, 13, 15, 17, 19, 21, 23, 25, 27, 29.
We will design an algorithm to solve this problem and implement the code.

Problem-Solving Approach

Let’s approach the problem step by step.
First, since we only consider cases where n is a natural number with two or more digits, we will use a loop from 10 to n to calculate the sum of the digits for each number.

To calculate the sum of the digits of a number, we can convert the given number to a string,
then convert each digit back to an integer and add it to the total sum.
Next, we will check if that sum is odd, and if so, add the number to the list of good numbers.

Code Implementation

Now, let’s implement the algorithm described above in Python.


def is_good_number(num):
    # Calculate the sum of the digits and check if it is odd
    digit_sum = sum(int(digit) for digit in str(num))
    return digit_sum % 2 == 1

def find_good_numbers(n):
    good_numbers = []
    for i in range(10, n + 1):  # digits with two or more
        if is_good_number(i):
            good_numbers.append(i)
    return good_numbers

# Test
n = 30
print(find_good_numbers(n))
            

The above code uses a function called is_good_number to calculate the sum of the digits of a given number and check
whether that sum is odd.

find_good_numbers function loops through all numbers from 10 to n,
finding “good numbers” and adding them to a list to return.

Execution Result

The result of executing the function for n = 30 is as follows:


[11, 13, 15, 17, 19, 21, 23, 25, 27, 29]
            

As shown above, the “good numbers” 11, 13, 15, 17, 19, 21, 23, 25, 27, 29 have been returned.
This result satisfies the condition of being “natural numbers with two or more digits whose sum of digits is odd.”

Complexity Analysis

Looking at the time complexity of the above algorithm,
since we have to iterate through all the numbers up to the given n, the time complexity is O(n).
For each number, we need additional constant time to calculate the sum of its digits, so overall it can be confirmed as O(n).

The space complexity is O(k)
(where k is the number of “good numbers”) due to the list used to store the “good numbers.”
In practice, since the smaller n is, the fewer “good numbers” will be stored, thus allowing for efficient space usage.

Conclusion

Through this problem, we have learned a method to effectively find “good numbers” by considering counterexamples and calculating the sum of the digits.
We were able to explore various ways to improve efficiency through a step-by-step approach to problem-solving and
implementation using Python code, as well as complexity analysis.

In the future, practice various ways of thinking and solving through more algorithm problems.
Understanding and approaching problems is very important in coding tests.

python coding test course, K-th shortest path search

Understanding and practicing algorithm problem solving is essential for software developers, especially for students aiming for employment. By solving various types of problems, one must develop problem-solving skills and learn how to practically apply algorithms and data structures. In this course, we will cover the topic of “Finding the Kth Shortest Path” and explain the process of solving this problem in Python in detail.

Problem Description

Given a graph, the problem is to find the Kth shortest path between two specific nodes. The Kth shortest path means the Kth shortest path among the shortest paths between the two nodes. This problem can be approached through variations of shortest path algorithms like Dijkstra’s algorithm.

Problem Summary

  • The graph can be a directed graph with or without weights.
  • A graph is given (number of nodes N, number of edges M).
  • The starting node S and the ending node E are given.
  • The Kth shortest path needs to be found.

Input Format

N M K
a1 b1 c1
a2 b2 c2
...

In the input format above, N is the number of nodes, M is the number of edges, and K is the Kth shortest path you want to find. Each edge is given by three numbers: a is the starting node, b is the ending node, and c is the weight.

Output Format

The length of the Kth shortest path, or -1 if the Kth shortest path does not exist

Examples

Input Example

4 5 2
1 2 4
1 3 2
2 3 5
2 4 1
3 4 8

Output Example

6

Solution Process

To solve this problem, we need to implement an algorithm to find the Kth shortest path. We plan to modify a typical shortest path algorithm, Dijkstra’s algorithm, and devise a way to manage the costs of each path. Here, we will solve it by exploring paths using a Priority Queue.

Step-by-step Approach

Step 1: Representing the Graph

We will represent the graph in the form of an adjacency list. We will use a dictionary or list to hold information about each node and edge.

from collections import defaultdict
import heapq

def build_graph(edges):
    graph = defaultdict(list)
    for (u, v, w) in edges:
        graph[u].append((v, w))
    return graph

Step 2: Path Exploration Using a Priority Queue

We will use a priority queue to explore the shortest paths. The costs of each path and nodes can be saved and managed in tuple form. At this point, we should prepare a list to store K paths.

def kth_shortest_path(graph, start, end, k):
    # Initialization
    pq = []
    heapq.heappush(pq, (0, start))  # (cost, node)
    paths = defaultdict(list)

    while pq:
        cost, node = heapq.heappop(pq)
        
        # If the Kth path is found
        if node == end:
            paths[end].append(cost)
            if len(paths[end]) >= k:
                break
        
        for neighbor, weight in graph[node]:
            new_cost = cost + weight
            heapq.heappush(pq, (new_cost, neighbor))
    
    return paths[end][k - 1] if len(paths[end]) >= k else -1

Step 3: Integrating the Overall Algorithm

We will combine the above two functionalities to complete the overall algorithm. We will receive input, create the graph, and implement the logic for finding the Kth shortest path.

def main():
    N, M, K = map(int, input().split())
    edges = [tuple(map(int, input().split())) for _ in range(M)]
    graph = build_graph(edges)
    result = kth_shortest_path(graph, 1, N, K)
    print(result)

if __name__ == "__main__":
    main()

Conclusion and Wrap-up

In this course, we have covered the Kth shortest path finding problem. We understood the structure of the graph and the variations of Dijkstra’s algorithm, and learned how to explore paths through a priority queue. As algorithm problems can be variously modified, it’s important to practice solving many different problems.

Now, you have laid the foundation to solve the given problem. The methods learned will be applicable not only to the Kth shortest path problem but also to various other graph-related problems, so developing your application skills is important. I hope you continue to practice more algorithms and problem-solving methods.

Happy Coding!

Python Coding Test Course, Finding the K-th Number

In this course, we will learn about preparing for coding tests using Python through the algorithm problem “Finding the Kth Number.” This problem requires basic sorting and list manipulation, making it useful for practicing relevant basic syntax and algorithm techniques.

Problem Description

The problem is as follows:

n: Number of integers
k: The Kth number to find
arr: A list of n integers

1. Find the k-th number in the list.
2. Note that the numbers are positive integers, and the conditions are 1 ≤ n ≤ 1000, 1 ≤ k ≤ n.
3. The k-th number refers to the number at the k-th position in ascending order.

Example

Assuming the following input values are given:

n = 5
k = 2
arr = [5, 2, 3, 1, 4]

For the above input, the output should be as follows:

The 2nd number is 2.

Problem-Solving Strategy

To solve this problem, you can approach it in the following steps:

  1. Input: Obtain the values of n, k, and arr from the user.
  2. Sort: Sort the list arr in ascending order.
  3. Find Kth Number: Since the indexing of the list starts from 0, extract the value at the index k-1 and output it.

Code Implementation

Now, let’s implement the actual code based on the above-solving strategy.

def find_kth_number(n, k, arr):
    # 1. Sort the list
    arr.sort()
    
    # 2. Find the Kth number
    return arr[k - 1]

# Input
n = int(input("Enter the number of integers: "))
k = int(input("Enter the Kth number to find: "))
arr = list(map(int, input("Enter the list of integers (separated by spaces): ").split()))

# Find the Kth number
kth_number = find_kth_number(n, k, arr)
print(f"The {k}th number is {kth_number}.")

Code Explanation

The above code can be explained in three parts:

  1. Function Definition: The find_kth_number function is defined to take n, k, and arr as parameters. This function returns the k-th number.
  2. Sorting: The list is sorted in ascending order using arr.sort().
  3. Return Result: The k-th number is returned through return arr[k - 1]. Since k is taken as user input, k-1 is used to match the 0-based indexing.

Design Considerations

When solving the problem, there are several factors to consider. It is advisable to think about these points before writing the code:

  • Input Validation: It is essential to check if the values of n and k are given within the specified range.
  • Handling Duplicates: When there are duplicate values, it is good to clarify which number to select among the multiple k-th numbers.
  • Time Complexity: Sorting the arr takes O(n log n) time, so choosing an efficient algorithm is necessary.

Additional Practice Problems

If you have learned the basic algorithm for finding the Kth number through this problem, you can further develop your design skills by solving similar problems. Here are additional practice problems:

  1. Find the k-th smallest number among the numbers from 1 to n in a given list.
  2. Given two sorted lists, find the Kth number in the merged list of the two lists.
  3. Solve the problem of searching for the k-th smallest number in a 2D array.

Conclusion

In this course, we have explored the algorithm problem of Finding the Kth Number using Python. When solving algorithm problems, it is essential to clearly understand the problem, devise a solution strategy, and then implement it in code. Through practice, I hope you encounter various types of problems and improve your skills in solving them.

Thank you.

Python Coding Test Course, DNA Password

Many people are solving algorithm problems to prepare for coding tests. In this course, we will explore the DNA password problem and explain step by step how to solve it.

Problem Description

The DNA password consists of a specific pattern of combinations. The problem is to find how many substrings from the given DNA string can become passwords. Here are the details of the problem.

Problem Definition

From the given DNA string, count the number of distinct substrings that contain only the characters “””ACGT””” and are of length K or more.

Input

  • The first line contains the DNA string. (1 ≤ string length ≤ 1000)
  • The second line contains the value of K. (1 ≤ K ≤ 100)

Output

Output the total number of distinct substrings.

Problem Solving Process

To solve this problem efficiently, we will follow these steps:

Step 1: Generate Substrings

Generate all substrings of length K or more from the given DNA string. To do this, we will iterate over the starting indexes of the string and extract substrings of length K or more from each starting index.

Step 2: Store Unique Substrings

Store the generated substrings in a set. Since a set does not allow duplicates, only distinct substrings will be stored. We can utilize the set data type in this process.

Step 3: Output Result

Finally, we count the number of substrings stored in the set and output the result. Now let’s convert these processes into code.

Python Code


def count_unique_dna_substrings(dna: str, k: int) -> int:
    unique_substrings = set()  # Set to store substrings
    n = len(dna)

    # Extract substrings for all starting positions in the string
    for start in range(n):
        for end in range(start + k, n + 1):  # Only consider lengths >= K
            substring = dna[start:end]
            unique_substrings.add(substring)

    return len(unique_substrings)  # Return the count of unique substrings

# Input
if __name__ == "__main__":
    dna_string = input("Please enter the DNA string: ")
    k_value = int(input("Please enter the length K: "))
    
    # Calculate and print the number of unique substrings
    result = count_unique_dna_substrings(dna_string, k_value)
    print(f"The number of distinct substrings is: {result}")
    

Code Explanation

The function count_unique_dna_substrings used in the above code takes two parameters: the dna string and the k value. This function first creates an empty set to prepare to store substrings. It then extracts substrings through a loop that will be explained in the next step.

Loop Explanation

The first loop iterates over the starting index (start) of the DNA string. The second loop extracts substrings of length K or more starting from the start index, thus determining the end index. Each substring is added to the set, only storing distinct cases.

Example for Understanding

For example, if the input DNA string is ACGTACGT and the value of K is 2, the possible substrings that can be generated are as follows:

  • AC, ACG, ACGT, ACGTA, ACGTAC, ACGTACGT
  • CG, CGT, CGTA, CGTAC, CGTACG
  • GT, GTA, GTAC, GTACG
  • TA, TAC, TACG
  • AC, ACG, ACGT

In total, there will be 14 distinct substrings.

Performance Considerations

The above code checks all substring lengths for every starting index, resulting in a worst-case time complexity of O(n^3). Performance issues may occur when the string length reaches 1000. To enhance performance in extreme cases, we can consider applying the Sliding Window technique.

Application of Sliding Window Technique

Using the Sliding Window technique allows us to easily calculate the number of substrings with length >= K in a single pass. It means storing information about the length of the string during one pass to facilitate exploration. This technique can reduce time complexity to O(n^2) or O(n).

Conclusion

In this course, we learned how to handle substrings of a string and resolve duplication issues using sets through the DNA password problem. Such problems frequently appear in coding tests, so it is recommended to be well-versed in basic string handling methods. In the next course, we will cover another useful algorithm problem.

Stay tuned for the next course! Thank you for your interest and support.

Python Coding Test, DFS and BFS Programs

1. Introduction

Recently, many companies have been conducting coding tests using algorithms. In these coding tests,
graph traversal algorithms like DFS (Depth-First Search) and BFS (Breadth-First Search) are frequently featured.
This article will summarize the basic concepts of DFS and BFS, and based on this, present an algorithm problem
and explain the problem-solving process in detail.

2. Overview of DFS and BFS

2.1 DFS (Depth-First Search)

DFS is a method that starts from a vertex of the graph and explores as far as possible along each branch
before backtracking to the last visited vertex to continue the exploration. It can be implemented using
a stack data structure or a recursive function. The process of DFS is as follows:

  • Add the starting vertex to the stack and mark it as visited.
  • Pop a vertex from the top of the stack and add an unvisited adjacent vertex to the stack.
  • Repeat the above steps until all vertices have been explored.

2.2 BFS (Breadth-First Search)

BFS is a method that starts from a vertex of the graph and explores all its adjacent vertices first,
and then explores the adjacent vertices of those adjacent vertices. It is implemented using a queue data structure.
The process of BFS is as follows:

  • Add the starting vertex to the queue and mark it as visited.
  • Dequeue a vertex and add unvisited adjacent vertices of that vertex to the queue.
  • Repeat the above steps until all vertices have been explored.

3. Introduction to Algorithm Problem

Problem: Path Finding

Here is a problem of finding a path using DFS and BFS.
Problem Statement: Given a graph and two vertices A and B,
check if there exists a path from A to B.
The graph is provided in the form of an adjacency list.

        input:
        graph = {
            'A': ['B', 'C'],
            'B': ['D'],
            'C': ['D', 'E'],
            'D': ['F'],
            'E': [],
            'F': []
        }
        start = 'A'
        end = 'F'
        output: True (There exists a path from A to F)
    

4. Problem-Solving Process

4.1 Finding Path using DFS

The method to find a path using DFS is as follows.
It can be implemented using a recursive function and a set data structure can be used to keep track of visited vertices.

        def dfs(graph, start, end, visited):
            if start == end:
                return True
            
            visited.add(start)
            
            for neighbor in graph[start]:
                if neighbor not in visited:
                    if dfs(graph, neighbor, end, visited):
                        return True
            
            return False
        
        graph = {
            'A': ['B', 'C'],
            'B': ['D'],
            'C': ['D', 'E'],
            'D': ['F'],
            'E': [],
            'F': []
        }
        start = 'A'
        end = 'F'
        visited = set()
        result = dfs(graph, start, end, visited)
    

The above code allows for path finding using DFS.
Initially, when the starting vertex A is given, it checks the visit status of that vertex and then explores the adjacent vertices
using recursive calls. If it reaches vertex F and returns True, it signifies that a path exists.

4.2 Finding Path using BFS

The method to find a path using BFS involves maintaining a record of visited vertices using a queue.

        from collections import deque
        
        def bfs(graph, start, end):
            queue = deque([start])
            visited = set([start])
            
            while queue:
                vertex = queue.popleft()
                
                if vertex == end:
                    return True
                
                for neighbor in graph[vertex]:
                    if neighbor not in visited:
                        visited.add(neighbor)
                        queue.append(neighbor)
                        
            return False
        
        graph = {
            'A': ['B', 'C'],
            'B': ['D'],
            'C': ['D', 'E'],
            'D': ['F'],
            'E': [],
            'F': []
        }
        start = 'A'
        end = 'F'
        result = bfs(graph, start, end)
    

Using the above code implemented for BFS, we can explore each adjacent vertex one by one
through the queue to check if there is a path from A to F.
The queue is used during the exploration process, and it also keeps track of visited vertices.

5. Conclusion

DFS and BFS are distinct search methods, each suitable for various problems depending on their characteristics.
If one wants to explore the depth of the structure first, DFS is advantageous, while BFS is preferable for finding the shortest path.
This article demonstrated the implementation and differences between the two algorithms through a simple path-finding problem.
Based on this foundation, one can attempt to tackle more complex algorithm problems.

6. References

  • Data Structures and Algorithms in Python by Michael T. Goodrich
  • Introduction to Algorithms by Thomas H. Cormen
  • Python Documentation: https://docs.python.org/3/