Kotlin coding test course, finding the largest square

Hello, and welcome to the coding test problem-solving course using Kotlin. In this session, we will solve the problem called “Finding the Largest Square.” The task is to find the area of the largest square in a given 2D array. Through this problem, you will learn the basic concept of Dynamic Programming and understand the syntax and features of the Kotlin language.

Problem Description

There is a given 2D binary array of size m x n. In this array, ‘1’ indicates that a square can be formed at that position, while ‘0’ indicates it cannot. The goal is to find the area of the largest square.

For example, consider the following array:

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

In this case, the area of the largest square is 4. (2×2 square)

Approach to the Problem

To solve this problem, we will use Dynamic Programming. Here are the main steps to solve the problem:

  1. Initialize DP Array: Create a DP array with the same size as the input 2D array. Each element in this DP array represents the length of the side of the square ending at position (i, j).
  2. Set State Transition Relation: If grid[i][j] is ‘1’, then DP[i][j] will be the minimum of DP[i-1][j], DP[i][j-1], and DP[i-1][j-1] + 1. This determines the length of the largest square at (i,j).
  3. Track Maximum Square Side Length: Through this process, track the side length of the largest square and finally calculate the area by squaring that value.

Code Implementation

Now let’s implement the above algorithm in Kotlin. Below is the code for the algorithm:


    fun maximalSquare(matrix: Array): Int {
        if (matrix.isEmpty()) return 0

        val m = matrix.size
        val n = matrix[0].size
        val dp = Array(m) { IntArray(n) }
        var maxSide = 0

        for (i in 0 until m) {
            for (j in 0 until n) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) {
                        dp[i][j] = 1 // For the first row or first column
                    } else {
                        dp[i][j] = minOf(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
                    }
                    maxSide = maxOf(maxSide, dp[i][j])
                }
            }
        }
        return maxSide * maxSide // Return the area of the square
    }
    

Code Explanation

The code takes a 2D array as input and calculates the length of the side of the square ending at each cell. Here is an explanation of the key parts of the code:

  • Initial Validation: Checks whether the input matrix is empty. If it is, returns 0.
  • Double Loop: Iterates through each element, updating the values in the DP table if it is ‘1’.
  • Maximum Side Update: Tracks the maximum value at each case and returns the final result by squaring it.

Complexity Analysis

The time complexity of this algorithm is O(m * n), and since it uses a DP array, the space complexity is also O(m * n). However, the space can be optimized. By using two variables instead of a DP array to store the information of the current row and the previous row, the space complexity can be reduced to O(n).

Optional Space Optimization Code


    fun maximalSquare(matrix: Array): Int {
        if (matrix.isEmpty()) return 0
        val m = matrix.size
        val n = matrix[0].size
        val dp = IntArray(n)
        var maxSide = 0
        var prev = 0

        for (i in 0 until m) {
            for (j in 0 until n) {
                val temp = dp[j]
                if (matrix[i][j] == '1') {
                    dp[j] = if (i == 0 || j == 0) 1 else minOf(dp[j], dp[j - 1], prev) + 1
                    maxSide = maxOf(maxSide, dp[j])
                } else {
                    dp[j] = 0
                }
                prev = temp
            }
        }
        return maxSide * maxSide
    }
    

Conclusion

In this course, we learned how to solve the “Finding the Largest Square” problem. In this process, we understood the basic concepts of Dynamic Programming and learned how to implement algorithms in the Kotlin language. These algorithmic problems will help improve problem-solving skills and greatly assist in preparing for coding tests. In the next session, we will cover another interesting algorithm problem. Thank you!

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.