Kotlin Coding Test Course, DNA Password

Coding tests play an important role in the hiring process of software developers, with many companies devising various problems to evaluate candidates’ algorithm problem-solving skills. In this chapter, we will take a closer look at how to solve the ‘DNA Password’ problem using Kotlin.

Problem Description

The DNA Password problem has the following conditions:

  • A DNA password is a string composed of the letters A, C, G, and T.
  • You need to find a substring in the given DNA string that meets specific conditions.
  • Each DNA string must contain at least M of specific characters.

For example, if the DNA string is “ACGTACGT” and M=2, then ‘A’ must appear at least twice, and ‘C’ must appear at least twice.

Example Problem

Given DNA string: ACGTACGT
Required character count: M=2
Required DNA characters: {‘A’, ‘C’}

Goal

The goal is to find all substrings in the given DNA string that satisfy the required characters and count them.

Problem-Solving Approach

To solve this problem, we will use the sliding window technique. This technique involves moving a window over the given string while checking the required character count.

Explanation of the Sliding Window Technique

The sliding window proceeds through the following steps:

  • Set the starting and ending points of the window and check if the initial state meets the conditions.
  • Move the window while checking the number of required characters at each position.
  • If the number of characters meets the condition, increase the count.

Kotlin Code Implementation

Here is the Kotlin code to solve the DNA Password problem:


fun countDnaPasswords(dna: String, requiredChars: Set, minCount: Int): Int {
    val charCount = mutableMapOf()
    var validPasswords = 0
    var left = 0

    for (right in dna.indices) {
        charCount[dna[right]] = charCount.getOrDefault(dna[right], 0) + 1

        while (requiredChars.all { charCount.getOrDefault(it, 0) >= minCount }) {
            validPasswords++
            charCount[dna[left]] = charCount[dna[left]]!! - 1
            if (charCount[dna[left]] == 0) {
                charCount.remove(dna[left])
            }
            left++
        }
    }

    return validPasswords
}

// Example usage
fun main() {
    val dna = "ACGTACGT"
    val requiredChars = setOf('A', 'C')
    val minCount = 2
    println(countDnaPasswords(dna, requiredChars, minCount))  // Output: 6
}

Code Explanation

The code above operates with the following logic:

  • Function Definition: The countDnaPasswords function takes the DNA string, a set of required characters, and the minimum number of required characters as parameters.
  • Initialization of Count: It initializes a map to count the occurrence of required characters.
  • Sliding Window Implementation: It traverses the string using a right pointer while updating the count of each character.
  • Condition Check: When all required characters are satisfied, it increments the count and moves the left pointer to reduce the window.

Complexity Analysis

The time complexity of the above algorithm is O(n). This is because it checks the required characters by traversing the string once. The additional space complexity is O(1), which may vary depending on the number of required characters, but since it stores a maximum of 4 characters, it is constant space.

Conclusion

In this post, we learned the sliding window technique through the DNA Password problem and explored how to solve it using Kotlin. These algorithm problems are very useful in the job preparation process and will help with coding test preparation. It is recommended to modify and apply the code to various situations and practice more problems.

In the next post, we will cover a wider range of algorithm problems, so please stay tuned!

Kotlin coding test course, DFS and BFS programs

Introduction

Learning various algorithms while preparing for coding tests is very important. Among them,
DFS (Depth-First Search) and BFS (Breadth-First Search) algorithms are effective for solving many problems.
In this article, we will understand the concepts of DFS and BFS and learn how to prepare for actual coding tests through examples using these algorithms.

Concepts of DFS and BFS

DFS and BFS are algorithms for graph traversal, and the approaches of the two algorithms are different from each other.

DFS (Depth-First Search)

DFS is a depth-first search that explores as deeply as possible from one node before returning to the previous node
to explore other nodes when no further exploration is possible. It is generally implemented using a stack structure.

BFS (Breadth-First Search)

BFS is a breadth-first search that explores all neighboring nodes of one node before moving on to explore nodes at the next depth.
It is typically implemented using a queue structure.

Problem Description

Here is an example problem that can be solved using DFS and BFS.
Problem: Determine whether two nodes in a given graph are connected.

Input

  • Number of nodes N (1 ≤ N ≤ 1000)
  • Number of edges E (1 ≤ E ≤ 10000)
  • Pairs of two nodes representing each edge (A, B)
  • Query Q (number of queries to check if two nodes are connected)
  • For each query, two nodes X, Y to check for connectivity

Output

For each query, print “YES” if X and Y are connected, otherwise print “NO”.

Problem Solving Process

1. Graph Representation

The graph can be represented in the form of an adjacency list. Connected nodes are added to the list to determine the existence or lack thereof.

2. Implementing DFS

We will implement DFS through recursive calls. A boolean array is used to check visited nodes, and we will explore
starting from the given node until we reach the target node.


fun dfs(graph: List>, visited: BooleanArray, current: Int, target: Int): Boolean {
    if (current == target) return true
    visited[current] = true

    for (neighbor in graph[current]) {
        if (!visited[neighbor]) {
            if (dfs(graph, visited, neighbor, target)) return true
        }
    }
    return false
}
    

3. Implementing BFS

BFS is implemented using a queue. We add the starting node to the queue, and for each node, we take it out of the queue one by one
to visit all neighboring nodes and check if we reach the target node.


fun bfs(graph: List>, start: Int, target: Int): Boolean {
    val queue: Queue = LinkedList()
    val visited = BooleanArray(graph.size)
    
    queue.offer(start)
    visited[start] = true

    while (queue.isNotEmpty()) {
        val current = queue.poll()
        if (current == target) return true

        for (neighbor in graph[current]) {
            if (!visited[neighbor]) {
                visited[neighbor] = true
                queue.offer(neighbor)
            }
        }
    }
    return false
}
    

4. Complete Code

Now we will write the complete code that takes the input of the graph and determines connectivity using DFS and BFS.


import java.util.*

fun main() {
    val scanner = Scanner(System.`in`)
    val n = scanner.nextInt()
    val e = scanner.nextInt()

    val graph = List(n) { mutableListOf() }

    for (i in 0 until e) {
        val a = scanner.nextInt() - 1
        val b = scanner.nextInt() - 1
        graph[a].add(b)
        graph[b].add(a)  // Undirected graph
    }

    val q = scanner.nextInt()
    for (i in 0 until q) {
        val x = scanner.nextInt() - 1
        val y = scanner.nextInt() - 1

        val visitedDFS = BooleanArray(n)
        val visitedBFS = BooleanArray(n)

        // DFS Check
        val isConnectedDFS = dfs(graph, visitedDFS, x, y)
        // BFS Check
        val isConnectedBFS = bfs(graph, x, y)

        println("Connected in DFS: ${if (isConnectedDFS) "YES" else "NO"}")
        println("Connected in BFS: ${if (isConnectedBFS) "YES" else "NO"}")
    }
}
    

Conclusion

DFS and BFS each have their strengths and weaknesses, and it is important to choose the right algorithm depending on the specific problem.
I hope this tutorial helped you understand the two algorithms and will aid you in preparing for actual coding tests.

Kotlin Coding Test, Let’s try DDR

In this post, we will discuss a problem related to the ‘DDR’ game, which is frequently presented as one of the algorithm problems for job interviews. This problem involves basic array manipulation and requires algorithmic thinking, making it very helpful for coding test preparation. We will examine the problem definition and the process of solving it in detail.

Problem Definition

You are a fan of the ‘DDR (Dance Dance Revolution)’ game. In this game, arrows appear in various directions (up, down, left, right) on a rectangular step pad. The step pad is composed of a N * N grid. Arrows can appear at various positions on the grid, and according to certain rules, the grid is filled, and the arrows must be compressed upwards.

You need to solve the problem of returning the compressed state based on the given initial state of the DDR step pad. The following rules apply:

  • The game pad is of size N * N, and each position is represented by 0 (empty) or 1 (arrow).
  • You must return the arrows that appear by compressing downwards in all columns. In other words, you need to push down by column.

Input and Output Format

Input:

  1. The first line contains the size of the step pad N (1 ≤ N ≤ 100).
  2. The next N lines contain the state of the step pad. Each consists of N space-separated numbers made up of 0 and 1.

Output:

Print the compressed state of the step pad over N lines. Each line consists of 0 and 1, separated by spaces.

Example

Input

    5
    0 0 0 0 0
    1 0 1 0 0
    1 1 0 1 0
    0 0 0 1 1
    0 0 0 0 0
    

Output

    0 0 0 0 0
    0 0 1 1 1
    1 1 1 0 0
    1 0 0 0 0
    1 0 0 0 0
    

Problem Solving Process

Now, let’s plan the algorithm to solve the given problem. This problem requires us to implement the process of compressing downwards by column from the given grid. Let’s approach it step by step.

Step 1: Choosing the Data Structure

We will use a 2D array to store each input in a workspace. I will create a list for each column to count the number of 1s by checking each scene.

Step 2: Implementing the Compression Logic

We will check each column individually to count the number of 1s and perform the compression in a way that aligns the structure downwards. To do this, we will place 1s from the bottom in a newly structured array and fill the rest with 0s.

Step 3: Writing Kotlin Code

Now, let’s implement the above process in Kotlin. Here is the implemented code:

    fun compressDDRPads(n: Int, pads: Array): Array {
        // Initialize a 2D array to store the result
        val result = Array(n) { IntArray(n) }

        // Iterate through each column to compress downwards
        for (j in 0 until n) {
            var count = 0

            // Loop through the column to count the number of 1s
            for (i in 0 until n) {
                if (pads[i][j] == 1) {
                    count++
                }
            }

            // Fill the compressed result from the bottom
            for (i in n - 1 downTo n - count) {
                result[i][j] = 1
            }
        }

        return result
    }

    // Example input
    fun main() {
        val n = 5
        val pads = arrayOf(
            intArrayOf(0, 0, 0, 0, 0),
            intArrayOf(1, 0, 1, 0, 0),
            intArrayOf(1, 1, 0, 1, 0),
            intArrayOf(0, 0, 0, 1, 1),
            intArrayOf(0, 0, 0, 0, 0)
        )

        val compressedPads = compressDDRPads(n, pads)

        // Print the result
        for (row in compressedPads) {
            println(row.joinToString(" "))
        }
    }
    

Conclusion

In this post, we explored the process of solving the DDR problem using Kotlin. We found that understanding algorithmic thinking and data structures is crucial in the problem-solving process. By repeatedly practicing such problems in actual coding tests, you will find it easier to solve them. I hope to deepen my coding skills through various algorithm problems in the future.

References

kotlin coding test course, Ax + By = C

One of the mathematical problems that frequently appears in recent coding tests is related to equations of the form Ax + By = C. In this tutorial, we will analyze this problem and explain in detail how to solve it using Kotlin.

Problem Description

We are given integers with the following conditions:

  • A, B, and C are integers, and 1 <= |A|, |B|, |C| <= 1000.
  • x and y are non-negative integers.

Find all pairs (x, y) that satisfy Ax + By = C for the given A, B, and C.

Input Format

The first line contains A, B, and C separated by spaces.

Output Format

Output all pairs (x, y) that satisfy Ax + By = C. The pairs should be sorted in ascending order, and in case of equal x values, they should be sorted based on the y value.

Example Input

2 3 6

Example Output

0 2
1 1
2 0

Approach to the Problem

To solve this problem, let’s first discuss the method of finding integer x and y that satisfy the condition Ax + By = C. To find the pairs (x, y) that satisfy this condition, we follow these steps:

  1. Set up a loop: Change the value of x from 0 to C/A (be cautious if C is not divisible by A) and check. The value of y can be calculated from C – Ax.
  2. Check integer conditions: The calculated y must be non-negative and an integer. Verify this condition.
  3. Store and print results: Store the (x, y) pairs that satisfy the condition in a list and finally sort and print them.

Kotlin Implementation

fun findSolutions(A: Int, B: Int, C: Int) {
    val solutions = mutableListOf>()
    
    for(x in 0..C / A) {
        val yValue = (C - (A * x)) / B
        
        // Add (x, y) pair if conditions are met
        if (yValue >= 0 && (C - A * x) % B == 0) {
            solutions.add(Pair(x, yValue))
        }
    }

    // Sort
    solutions.sortWith(compareBy({ it.first }, { it.second }))
    
    // Print
    for (solution in solutions) {
        println("${solution.first} ${solution.second}")
    }
}

Complete Code

fun main() {
    val input = readLine()!!.split(" ")
    val A = input[0].toInt()
    val B = input[1].toInt()
    val C = input[2].toInt()
    
    findSolutions(A, B, C)
}

Conclusion

In this tutorial, we explored the algorithm and its implementation for solving the Ax + By = C problem. Problems of this type frequently appear in coding tests, and it is essential to understand the structure of the problem and clarify the approach. By practicing problems like this, you can develop your algorithmic thinking and strengthen your ability to solve various problems using Kotlin.

Additional Learning Resources

Кotlin Coding Test Course, Calculating ATM Withdrawal Time

Written on: October 17, 2023

Author: Algorithm Expert

Table of Contents

  1. 1. Problem Description
  2. 2. Input Format
  3. 3. Output Format
  4. 4. Example
  5. 5. Approach
  6. 6. Code Implementation
  7. 7. Conclusion

1. Problem Description

You are a user of an ATM. There are several users waiting at the ATM, and each user is in line to withdraw money from their account.

The time it takes for each user to withdraw money can vary. The goal of this problem is to calculate the total time it takes for all users to complete their withdrawals.

The input provided to you is a list of withdrawal times for each user. All users will use the ATM in order, and the next user can only use it after the user in front has finished.

The total withdrawal time is the sum of the time each user spends waiting in front of the ATM and the time taken to complete the withdrawal. You will write a program in Kotlin to calculate this.

2. Input Format

The first line contains the number of users N (1 <= N <= 1,000).

The second line contains the withdrawal times for each user, separated by spaces. The withdrawal time is at least 1 second and at most 1,000 seconds.

3. Output Format

Print the total withdrawal time for all users in one line.

4. Example

Example Input

5
3 1 4 3 2
        

Example Output

32
        

Description

When the withdrawal times for each user are given, calculate the waiting time and withdrawal time in order.

User 1: 3 seconds

User 2: 3 seconds + 1 second = 4 seconds

User 3: 4 seconds + 4 seconds = 8 seconds

User 4: 8 seconds + 3 seconds = 11 seconds

User 5: 11 seconds + 2 seconds = 13 seconds

Total time = 3 + 4 + 8 + 11 + 13 = 32 seconds

5. Approach

To solve this problem, you can approach it by sequentially accumulating the withdrawal times to calculate the total time.
The specific approach to solving the problem is as follows:

  1. Input the number of users N and the withdrawal times as a list.
  2. Sort this list. (It can be processed differently according to the problem’s requirements)
  3. Calculate the total withdrawal time by accumulating each user’s withdrawal time.
  4. Finally, output the total time.

6. Code Implementation

Below is the Kotlin code for solving the problem.

fun main() {
    val n = readLine()!!.toInt()
    val times = readLine()!!.split(" ").map { it.toInt() }.sorted()
    var totalTime = 0
    var accumulateTime = 0
    
    for (time in times) {
        accumulateTime += time
        totalTime += accumulateTime
    }
    
    println(totalTime)
}
        

Code Explanation

– First, read the number of users N, then read the withdrawal times as a list on the next line.
– After sorting the input times, calculate the total time by accumulating each user’s withdrawal time.
– Finally, print the result.

7. Conclusion

In this lecture, we covered the problem of calculating ATM withdrawal times. It is essential in algorithm problem-solving to understand the problem accurately and to clarify the approach.

Additionally, it is crucial to implement the code through languages like Kotlin, as it allows you to gain practical experience alongside theoretical knowledge.
Such practice will help you achieve high scores in coding tests.

I hope you continue to hone your algorithmic thinking by solving various problems.