Kotlin coding test course, finding the fastest bus route

As part of the coding test course using Kotlin, we will tackle the problem of finding the fastest bus route. In modern society, public transportation is an important means of transportation, and understanding the travel routes by bus is a feature that many people need. In this problem, we will design and implement an algorithm that calculates the shortest time according to the given conditions.

Problem Definition

Several bus routes are given as follows. Each route includes a starting station, an arrival station, and the time taken. Your goal is to find the fastest time taken from a specific starting station to a specific arrival station.

Problem Input

- Number of stations N (1 ≤ N ≤ 100)
- Number of routes M (1 ≤ M ≤ 1000)
- Each route information: starting station A, arrival station B, time taken T (1 ≤ A, B ≤ N, 1 ≤ T ≤ 1000)
- Starting station S and arrival station D

Problem Output

- The fastest time taken to go from the starting station to the arriving station. Output -1 if it is impossible.

Problem Solving Strategy

This problem is about finding the shortest path, and an appropriate algorithm to use is Dijkstra’s shortest path algorithm. The Dijkstra algorithm is useful for finding the shortest path from a starting vertex to all other vertices in a weighted graph. We will build a graph using the bus route information and use that graph to calculate the shortest time.

Step-by-Step Approach

Step 1: Define Input Data Structure

First, based on the provided route information, we will decide how to represent the graph. It is efficient to manage each station’s associated route information using an adjacency list.

val graph: Array>> = Array(N + 1) { mutableListOf() }

Step 2: Process Input Data

Convert the route information received from the user into graph form. Add each route information to the graph in the appropriate format.

for (i in 0 until M) {
    val (A, B, T) = readLine()!!.split(" ").map { it.toInt() }
    graph[A].add(Pair(B, T))
}

Step 3: Implement Dijkstra’s Algorithm

Use Dijkstra’s algorithm to calculate the shortest path for all stations. A priority queue is used to process the route with the least current time first.

fun dijkstra(start: Int): IntArray {
    val distances = IntArray(N + 1) { Int.MAX_VALUE }
    distances[start] = 0
    val priorityQueue = PriorityQueue>(compareBy { it.second })
    priorityQueue.add(Pair(start, 0))

    while (priorityQueue.isNotEmpty()) {
        val (currentStation, currentTime) = priorityQueue.poll()

        if (currentTime > distances[currentStation]) continue

        for (edge in graph[currentStation]) {
            val (nextStation, travelTime) = edge
            val newTime = currentTime + travelTime

            if (newTime < distances[nextStation]) {
                distances[nextStation] = newTime
                priorityQueue.add(Pair(nextStation, newTime))
            }
        }
    }
    return distances
}

Step 4: Output Result

Finally, output the shortest time taken to go from the starting station to the arrival station. If it is not possible to reach the arrival station, output -1.

val distances = dijkstra(S)
println(if (distances[D] == Int.MAX_VALUE) -1 else distances[D])

Complete Code Example

fun main() {
    val (N, M) = readLine()!!.split(" ").map { it.toInt() }
    val graph: Array>> = Array(N + 1) { mutableListOf() }

    for (i in 0 until M) {
        val (A, B, T) = readLine()!!.split(" ").map { it.toInt() }
        graph[A].add(Pair(B, T))
    }

    val (S, D) = readLine()!!.split(" ").map { it.toInt() }
    val distances = dijkstra(S)

    println(if (distances[D] == Int.MAX_VALUE) -1 else distances[D])
}

fun dijkstra(start: Int): IntArray {
    val distances = IntArray(N + 1) { Int.MAX_VALUE }
    distances[start] = 0
    val priorityQueue = PriorityQueue>(compareBy { it.second })
    priorityQueue.add(Pair(start, 0))

    while (priorityQueue.isNotEmpty()) {
        val (currentStation, currentTime) = priorityQueue.poll()

        if (currentTime > distances[currentStation]) continue

        for (edge in graph[currentStation]) {
            val (nextStation, travelTime) = edge
            val newTime = currentTime + travelTime

            if (newTime < distances[nextStation]) {
                distances[nextStation] = newTime
                priorityQueue.add(Pair(nextStation, newTime))
            }
        }
    }
    return distances
}

Conclusion

In this lecture, we solved the problem of choosing the fastest bus route using Kotlin. We learned how to address the shortest path problem through the Dijkstra algorithm and how to implement the various elements of the problem in code. Algorithm problems can improve through practice, so it is recommended to continuously try solving various problems.

Note: This problem requires an understanding of graph theory, and it is also advisable to practice other shortest path algorithms like the Bellman-Ford algorithm.

kotlin coding test course, ‘Finding Good Numbers’

Hello! In this session, we will solve one of the coding test problems using Kotlin, which is to find a ‘good number’. In this article, I will explain the problem description, approach, algorithm design, and actual code step by step. We will also cover various examples along with optimization, so please stay with us until the end!

Problem Description

A ‘good number’ problem is to find the combinations of different numbers (A, B, C, …) from a given list of integers such that A + B + C = 0. For example, suppose we have the following array.

 [-1, 0, 1, 2, -1, -4] 

The combinations of numbers that sum to 0 in the above array are (-1, 0, 1) and (-1, -1, 2). We must find and return all such combinations.

Approach to the Problem

To solve this problem, we need to consider a few basic approaches.

  1. Sorting: By sorting the array, we can effectively handle the same numbers and apply algorithms such as the two-pointer technique or binary search.
  2. Duplicate Removal: We need to ensure that the same number is not added multiple times.
  3. Pointer Technique: Using two pointers based on the sorted list allows us to efficiently check each combination.

Algorithm Design

The algorithm consists of the following steps.

  1. Sort the input array.
  2. Then, for each element, use two pointers to check if the sum equals 0.
  3. Avoid duplicates when adding to the result list.

Code Implementation

Now, let’s implement the code. We will write it in Kotlin as follows:


        fun threeSum(nums: IntArray): List> {
            nums.sort() // Sort the array
            val res = mutableListOf>()
            for (i in nums.indices) {
                if (i > 0 && nums[i] == nums[i - 1]) continue // Check for duplicates
                var left = i + 1
                var right = nums.size - 1
                while (left < right) {
                    val sum = nums[i] + nums[left] + nums[right]
                    when {
                        sum < 0 -> left++      // Move left pointer if sum is less
                        sum > 0 -> right--     // Move right pointer if sum is greater
                        else -> {
                            res.add(listOf(nums[i], nums[left], nums[right])) // Add when sum is 0
                            while (left < right && nums[left] == nums[left + 1]) left++ // Check for duplicates
                            while (left < right && nums[right] == nums[right - 1]) right-- // Check for duplicates
                            left++
                            right--
                        }
                    }
                }
            }
            return res
        }
        

Code Analysis

Let's analyze each part of the code.

  1. Array Sorting: The array is sorted in ascending order using `nums.sort()`. This is a prerequisite for applying the two-pointer technique.
  2. Main Loop: We iterate based on the index, selecting each element. If there's a duplicate, we skip it.
  3. Two Pointers: Based on the selected element, left and right pointers are set. We calculate and compare the sum of the three elements while moving these pointers.

Optimization and Time Complexity

The time complexity of this algorithm is O(n^2). This is because the outer loop runs n times and the inner while loop can run up to n times. Therefore, as the size of the list increases, the time complexity can increase significantly, requiring efficient handling.

The space complexity is O(1), as no additional space is used other than the input. However, space is used to store the result list.

Examples and Output

I will help clarify with example input and output.


        val input = intArrayOf(-1, 0, 1, 2, -1, -4)
        val result = threeSum(input)
        println(result) // Output: [[-1, -1, 2], [-1, 0, 1]]
        

Conclusion

In this lecture, we explored the process of designing and implementing an algorithm to find a ‘good number’ using Kotlin. By utilizing sorting, duplicate checking, and the two-pointer technique, we effectively solved the problem. I hope this helps improve your algorithmic thinking and coding skills as you tackle similar problems.

In the future, take time to explore multiple solutions through various algorithm problems!

kotlin coding test course, finding the longest increasing subsequence

Hello! In this post, we will address the problem of “Longest Increasing Subsequence” that is frequently asked in coding tests. This problem is very helpful for understanding the basics of algorithms and building your skills. I will explain the theories for effectively solving the problem and the specific implementation process using Kotlin.

Problem Description

The task is to find the Longest Increasing Subsequence (LIS) in a given array. A subsequence is an array formed by selecting elements from the original array in order, while maintaining the order of the original array. An increasing sequence means the elements are sorted in ascending order.

Example

    Input: [10, 22, 9, 33, 21, 50, 41, 60, 80]
    Output: 6
    Explanation: An example of the longest increasing subsequence is [10, 22, 33, 50, 60, 80].
    

Approach to Solution

The basic approach to solving this problem is to use dynamic programming (DP). DP is a method for breaking down a problem into smaller subproblems and storing previous computation results to avoid repeating the same calculations.

Step 1: Initialize the DP Table

First, we initialize the DP table. We create an array to store the LIS length starting at each index of the input array. Initially, since each element itself forms a subsequence, all values are set to 1.

Step 2: Update the DP Table

Now, we compare all elements of the input array with each other. For each element arr[i], we check all previous elements arr[j] (j < i) and evaluate the following condition:

    if arr[i] > arr[j] then
        dp[i] = max(dp[i], dp[j] + 1)
    

Here, dp[i] is the length of the LIS that includes arr[i], and dp[j] + 1 represents the length if arr[j] is followed by arr[i].

Step 3: Derive the Result

After processing all elements, we find the largest value in the dp array and return the length of the LIS as the result.

Kotlin Implementation

Now, let’s implement it in Kotlin. Below is the code that follows the algorithm explained above.


fun longestIncreasingSubsequence(arr: IntArray): Int {
    val n = arr.size
    val dp = IntArray(n) { 1 } // initialize dp array

    for (i in 1 until n) {
        for (j in 0 until i) {
            if (arr[i] > arr[j]) {
                dp[i] = maxOf(dp[i], dp[j] + 1)
            }
        }
    }
    return dp.maxOrNull() ?: 1 // return the maximum value in dp array
}

fun main() {
    val arr = intArrayOf(10, 22, 9, 33, 21, 50, 41, 60, 80)
    val length = longestIncreasingSubsequence(arr)
    println("Length of the longest increasing subsequence: $length")
}
    

Complexity Analysis

As a result, the time complexity of this algorithm is O(n²). This is because a nested loop runs for n. The space complexity is O(n) as we need space to store the DP array.

Optimization Method

If higher performance is required, the LIS problem can be solved using binary search, achieving O(n log n). This method is implemented as follows:


import java.util.*

fun longestIncreasingSubsequenceOptimized(arr: IntArray): Int {
    if (arr.isEmpty()) return 0

    val tails = mutableListOf()

    for (num in arr) {
        val index = tails.binarySearch(num)
        if (index < 0) {
            val pos = -(index + 1)
            if (pos == tails.size) {
                tails.add(num)
            } else {
                tails[pos] = num
            }
        }
    }
    return tails.size
}
    

Here, the tails array stores the possible last elements of the LIS. Using binary search, it finds the appropriate position to insert or replace elements.

Conclusion

In this article, we explored the longest increasing subsequence problem using Kotlin. I detailed the approach to the problem and the implementation method, along with optimization techniques. Such problems are frequently asked in coding tests, so repeated practice is important. I will continue to share various algorithm problems. If you have any questions, please ask in the comments!

Kotlin Coding Test Course: Finding the K-th Shortest Path

In this tutorial, we will solve the algorithm problem of finding the K-th shortest path using Kotlin. This problem utilizes shortest path algorithms, which are an important part of graph theory. Calculating the K-th shortest path in a given graph is often considered a complex problem, but with the appropriate algorithms and data structures, it can be solved efficiently.

Problem Description

The problem is to find the K-th shortest path between two points A and B when a graph is given. The weights of the paths are positive, and K is set to be 1 or greater. Paths may overlap, and K starts from 1. Thus, K=1 refers to the shortest path, while K=2 refers to the second shortest path. If the K-th path does not exist, “NO PATH” should be output.

Example

Input:

4 5 2
1 2 1
1 3 4
2 3 2
2 4 6
3 4 3
        

Output:

3
        

In the above example, ‘4’ is the number of vertices, ‘5’ is the number of edges, and ‘2’ is the value of K. Then, the starting point, endpoint, and weight of each edge are provided. In this case, the distance of the second shortest path from 1 to 4 is 3.

Approach to Solve the Problem

To solve this problem, we will use Dijkstra’s algorithm along with a priority queue to find the K-th shortest path. This approach can be extended to find multiple shortest paths from a single starting point. The basic idea is to repeat the process of finding the shortest path K times and store the K-th length at each step.

1. Set up Data Structures

First, we need to set up a data structure to store the K-th shortest path information for each node. We can use a 2D array for this purpose. The index of each array represents the node ID, and the second index represents the K-th path.

val distance = Array(n + 1) { IntArray(k + 1) { Int.MAX_VALUE } }
    

2. Build the Graph

The graph will be represented in the form of an adjacency list. To do this, we will store the connected vertices and weights for each vertex. We will construct the graph from the input.

val graph = mutableListOf>()
for (i in 0 until n + 1) {
    graph.add(mutableListOf())
}
for (i in 0 until m) {
    val (u, v, w) = readLine()!!.split(" ").map { it.toInt() }
    graph[u].add(Edge(v, w))
}
    

3. Algorithm to Find the K-th Shortest Path

We will use a priority queue to find the shortest path. We will update the distance while adding each path to the queue. The distance array will be managed appropriately to store the K-th shortest path.

val queue = PriorityQueue(compareBy { it.weight })
distance[start][0] = 0
queue.add(Edge(start, 0))

while (queue.isNotEmpty()) {
    val current = queue.poll()
    val currentNode = current.node
    val currentWeight = current.weight
    
    for (edge in graph[currentNode]) {
        val nextNode = edge.node
        val newWeight = currentWeight + edge.weight
        if (distance[nextNode][k - 1] > newWeight) {
            distance[nextNode].sort()
            distance[nextNode][k - 1] = newWeight
            queue.add(Edge(nextNode, newWeight))
        }
    }
}
    

4. Output the Result

Finally, we will output the distance of the K-th shortest path. If the K-th path does not exist, we will output “NO PATH”.

if (distance[end][k - 1] == Int.MAX_VALUE) {
    println("NO PATH")
} else {
    println(distance[end][k - 1])
}
    

Full Code

fun main() {
    val (n, m, k) = readLine()!!.split(" ").map { it.toInt() }
    val graph = mutableListOf>()
    
    for (i in 0..n) {
        graph.add(mutableListOf())
    }
    
    for (i in 0 until m) {
        val (u, v, w) = readLine()!!.split(" ").map { it.toInt() }
        graph[u].add(Edge(v, w))
    }
    
    val distance = Array(n + 1) { IntArray(k + 1) { Int.MAX_VALUE } }
    val queue = PriorityQueue(compareBy { it.weight })
    
    distance[1][0] = 0
    queue.add(Edge(1, 0))

    while (queue.isNotEmpty()) {
        val current = queue.poll()
        val currentNode = current.node
        val currentWeight = current.weight
        
        for (edge in graph[currentNode]) {
            val nextNode = edge.node
            val newWeight = currentWeight + edge.weight

            if (distance[nextNode][k - 1] > newWeight) {
                distance[nextNode].sort()
                distance[nextNode][k - 1] = newWeight
                queue.add(Edge(nextNode, newWeight))
            }
        }
    }
    
    if (distance[n][k - 1] == Int.MAX_VALUE) {
        println("NO PATH")
    } else {
        println(distance[n][k - 1])
    }
}

data class Edge(val node: Int, val weight: Int)
    

Conclusion

Finding the K-th shortest path is a graph problem with various algorithmic approaches possible. In the above example, we explained how to find the K-th shortest path by modifying Dijkstra’s algorithm. In practice, a variety of algorithms or optimization techniques can be applied, and it is important to choose the right algorithm depending on the problem.

The K-th shortest path finding problem we learned in this tutorial is often encountered in coding interviews, so it is advisable to improve your skills through various practice problems.

kotlin coding test course, finding the Kth number

Hello! In this tutorial, we will take a closer look at the algorithm problem of finding the Kth number using Kotlin. The problem of finding the Kth number is useful for laying the foundation of data sorting and search algorithms. In this article, we will cover the definition of the problem, approaches, coding implementation, and optimization techniques comprehensively.

Problem Definition

The problem statement is as follows:

Given an array of natural numbers and a number K, find the Kth smallest number in the array.

  • Input: Array {5, 3, 8, 1, 2} and K=3
  • Output: 3

Problem Approaches

To solve this problem, we can use the following approaches:

  1. Sort the array and return the value at the K-1 index
  2. Use a heap data structure to efficiently find the Kth number
  3. Use the Quickselect algorithm to find the Kth number with an average time complexity of O(N)

1. Array Sorting

The method of sorting the array and finding the K-1 index is the most intuitive and easy to understand. However, it has a worst-case time complexity of O(N log N), so we need to look for more efficient methods.

2. Method Using Heap

The method of finding the Kth number using a heap involves storing the K minimum elements in a heap and then finding and returning the maximum value of the heap. This approach has a time complexity of O(N log K).

3. Quickselect

The Quickselect algorithm is a variation of the QuickSort partition technique to find the desired Kth number. This algorithm has an average time complexity of O(N).

Code Implementation

Now, let’s write code to solve the problem. In this example, we will use the most intuitive method of sorting the array to find the Kth number.

Kth Number Finding Code


    fun findKthNumber(arr: IntArray, k: Int): Int {
        // Sort the array
        val sortedArray = arr.sortedArray()
        // Return the Kth number (K starts from 1, so K-1)
        return sortedArray[k - 1]
    }

    fun main() {
        val arr = intArrayOf(5, 3, 8, 1, 2)
        val k = 3
        val result = findKthNumber(arr, k)
        println("Kth number: $result") // Output: Kth number: 3
    }
    

Examples and Explanation

The code above follows these steps:

  1. Define the findKthNumber function that takes an integer array and K as parameters.
  2. Sort the array and return the value at the K-1 index.
  3. Run the main function to print the result.

Optimizations and Other Approaches

The above solutions are basic, and in actual coding tests, you may need to handle more complex problems. Let’s discuss a few additional methods for problems like finding the Kth number that are sensitive to time complexity.

Method Using Heap


    import java.util.PriorityQueue

    fun findKthNumberUsingHeap(arr: IntArray, k: Int): Int {
        val minHeap = PriorityQueue(compareBy { -it })
        for (num in arr) {
            minHeap.offer(num)
            if (minHeap.size > k) {
                minHeap.poll() // Remove the maximum when exceeding K
            }
        }
        return minHeap.poll() // Return the Kth number
    }
    

Quickselect Algorithm


    fun quickselect(arr: IntArray, left: Int, right: Int, k: Int): Int {
        if (left == right) {
            return arr[left]
        }

        val pivotIndex = partition(arr, left, right)
        if (k == pivotIndex) {
            return arr[k]
        } else if (k < pivotIndex) {
            return quickselect(arr, left, pivotIndex - 1, k)
        } else {
            return quickselect(arr, pivotIndex + 1, right, k)
        }
    }

    fun partition(arr: IntArray, left: Int, right: Int): Int {
        val pivot = arr[right]
        var i = left
        for (j in left until right) {
            if (arr[j] < pivot) {
                swap(arr, i, j)
                i++
            }
        }
        swap(arr, i, right)
        return i
    }

    fun swap(arr: IntArray, i: Int, j: Int) {
        val temp = arr[i]
        arr[i] = arr[j]
        arr[j] = temp
    }

    fun findKthNumberQuickselect(arr: IntArray, k: Int): Int {
        return quickselect(arr, 0, arr.size - 1, k - 1)
    }
    

Conclusion

In this tutorial, we have tackled the problem of finding the Kth number using various methods. From the intuitive method of sorting the array to the heap and Quickselect algorithms, we have learned multiple approaches that deepen our understanding of algorithms. These types of problems frequently appear in coding tests, so it's advisable to try various methods.

Additionally, the problem of finding the Kth number is fundamental to sorting and searching algorithms, so if you are well-versed in these techniques, you can apply them effectively to various problems. Consider code implementation and time complexity to select efficient approaches.

References

  • Introduction to Algorithms, CLRS
  • LeetCode Problems
  • GeeksforGeeks.com