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!