코틀린 코딩테스트 강좌, 가장 빠른 버스 노선 구하기

코틀린을 활용한 코딩테스트 강좌의 일환으로, 가장 빠른 버스 노선을 구하는 문제를 다뤄보겠습니다. 현대 사회에서 대중교통은 중요한 이동 수단이며, 특히 버스를 이용한 이동 경로 파악은 많은 사람들이 필요로 하는 기능입니다. 본 문제에서는 주어진 조건에 따라 최단 시간을 계산하는 알고리즘을 설계하고 구현해볼 것입니다.

문제 정의

다음과 같이 여러 개의 버스 노선이 주어집니다. 각 노선은 시작 정류장, 도착 정류장, 소요 시간을 포함합니다. 여러분의 목표는 특정 시작 정류장에서 특정 도착 정류장까지의 가장 빠른 소요 시간을 구하는 것입니다.

문제 입력

- 정류장의 수 N (1 ≤ N ≤ 100)
- 노선의 수 M (1 ≤ M ≤ 1000)
- 각 노선 정보: 시작 정류장 A, 도착 정류장 B, 소요 시간 T (1 ≤ A, B ≤ N, 1 ≤ T ≤ 1000)
- 시작 정류장 S와 도착 정류장 D

문제 출력

- 시작 정류장에서 도착 정류장까지 가는 가장 빠른 소요 시간. 불가능한 경우 -1을 출력.

문제 해결 전략

본 문제는 최단 경로를 찾는 문제로, 적합한 알고리즘으로는 Dijkstra의 최단 경로 알고리즘을 사용할 수 있습니다. Dijkstra 알고리즘은 가중치가 있는 그래프에서 시작 정점으로부터 다른 모든 정점까지의 최단 경로를 찾는 데 유용합니다. 버스 노선 정보를 그래프로 구축하고, 해당 그래프를 활용하여 최단 시간을 계산할 것입니다.

단계별 접근 방법

1단계: 입력 데이터 구조 정의

우선, 주어진 노선 정보를 바탕으로 그래프를 어떤 형식으로 표현할지를 결정합니다. 인접 리스트를 사용하여 각 정류장과 연결된 노선 정보를 관리하는 것이 효율적입니다.

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

2단계: 입력 데이터 처리

사용자로부터 입력받은 노선 정보를 그래프 형태로 변환합니다. 각 노선 정보를 알맞은 형식으로 그래프에 추가합니다.

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

3단계: Dijkstra 알고리즘 구현

Dijkstra 알고리즘을 이용하여 모든 정류장에 대한 최단 경로를 계산합니다. 우선순위 큐를 사용하여 현재 소요 시간이 가장 적은 노선을 먼저 처리합니다.

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
}

4단계: 결과 출력

최종적으로 시작 정류장에서 도착 정류장까지의 최단 소요 시간을 출력합니다. 만약 도착 정류장에 도달할 수 없는 경우 -1을 출력합니다.

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

전체 코드 예제

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
}

결론

이번 강좌에서는 코틀린을 사용하여 가장 빠른 버스 노선을 선택하는 문제를 해결해 보았습니다. Dijkstra 알고리즘을 통해 최단 경로 문제를 해결하는 과정과, 문제의 여러 요소를 고려하여 코드로 구현하는 방법을 배울 수 있었습니다. 알고리즘 문제는 연습을 통해 더 나아질 수 있으므로, 지속적으로 다양한 문제를 풀어보는 것을 추천합니다.

참고: 이 문제는 그래프 이론에 대한 이해가 필요하며, Dijkstra 알고리즘 외에도 벨만-포드 알고리즘 등의 다른 최단 경로 알고리즘도 연습해 보시길 바랍니다.

코틀린 코딩테스트 강좌, 가장 큰 정사각형 찾기

안녕하세요, 코틀린을 활용한 코딩테스트 문제 풀이 강좌에 오신 것을 환영합니다. 이번 시간에는 “가장 큰 정사각형 찾기”라는 문제를 해결해보겠습니다. 이 문제는 주어진 2차원 배열에서 가장 큰 정사각형의 넓이를 찾는 과제입니다. 이 문제를 통해 동적 프로그래밍(Dynamic Programming)의 기초 개념을 익히고, 코틀린 언어의 문법과 특징도 이해할 수 있습니다.

문제 설명

주어진 m x n 크기의 2차원 이진 배열이 있습니다. 이 배열에서 ‘1’은 해당 위치에서 정사각형이 형성될 수 있음을 의미하고, ‘0’은 그렇지 않음을 의미합니다. 가장 큰 정사각형의 넓이를 찾는 것이 목표입니다.

예를 들어, 다음과 같은 배열이 있을 때:

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

이 경우, 가장 큰 정사각형의 넓이는 4입니다. (2×2 정사각형)

문제 접근 방법

이 문제를 해결하기 위해 우리는 동적 프로그래밍을 사용할 것입니다. 다음은 문제를 해결하기 위한 주요 단계입니다:

  1. DP 배열 초기화: 입력받은 2차원 배열과 같은 크기를 가지는 DP 배열을 생성합니다. 이 DP 배열의 각 원소는 (i, j) 위치에서 끝나는 정사각형의 한 변의 길이를 나타냅니다.
  2. 상태 전이 관계 설정: 만약 grid[i][j]가 ‘1’이라면, DP[i][j]는 DP[i-1][j], DP[i][j-1], DP[i-1][j-1] 중 최소값 + 1이 됩니다. 이는 (i,j)에서의 가장 큰 정사각형의 변 길이를 결정합니다.
  3. 최대 정사각형 변 길이 추적: 이 과정을 통해 가장 큰 정사각형의 변 길이를 추적하고, 마지막에 그 값을 제곱하여 넓이를 계산합니다.

코드 구현

이제 위의 알고리즘을 코틀린으로 구현해보겠습니다. 아래는 해당 알고리즘의 코드입니다:


    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 // 첫 행이나 첫 열의 경우
                    } 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 // 정사각형의 넓이 반환
    }
    

코드 설명

코드는 2차원 배열을 입력으로 받아, 각 셀에 대해 해당 위치에서 끝나는 정사각형의 한 변의 길이를 계산합니다. 다음은 코드의 주요 부분에 대한 설명입니다:

  • 초기 검증: 입력된 행렬이 비어있는지 검사합니다. 비어있다면 0을 반환합니다.
  • 이중 루프: 각 원소를 순회하며, ‘1’일 경우 DP 테이블에 값을 업데이트합니다.
  • 최대 사이드 업데이트: 각 경우 최댓값을 추적하여, 마지막에 이를 제곱하여 결과를 반환합니다.

복잡도 분석

이 알고리즘의 시간 복잡도는 O(m * n)이며, DP 배열을 사용하므로 공간 복잡도 또한 O(m * n)입니다. 하지만 공간을 최적화할 수도 있습니다. DP 배열 대신 두 개의 변수를 사용하여 현재 행과 이전 행의 정보를 저장함으로써 공간 복잡도를 O(n)으로 줄일 수 있습니다.

선택적 공간 최적화 코드


    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
    }
    

결론

이번 강좌를 통해 “가장 큰 정사각형 찾기” 문제를 해결하는 방법을 학습했습니다. 이 과정에서 동적 프로그래밍의 기본적인 개념을 이해하고, 코틀린 언어로 알고리즘을 구현하는 방법을 배웠습니다. 이러한 알고리즘 문제를 통해 문제 해결 능력을 향상시키고, 코딩 테스트 준비에 큰 도움이 될 것입니다. 다음 시간에는 또 다른 흥미로운 알고리즘 문제를 다뤄보겠습니다. 감사합니다!

코틀린 코딩테스트 강좌, 가장 길게 증가하는 부분 수열 찾기

안녕하세요! 이번 포스트에서는 코딩 테스트에서 자주 출제되는 “가장 길게 증가하는 부분 수열” 문제를 다뤄보겠습니다. 이 문제는 알고리즘의 기초를 이해하고, 실력을 쌓는 데 큰 도움이 됩니다. 문제를 효과적으로 풀기 위한 이론과 코틀린을 활용한 구체적인 구현 과정을 설명하겠습니다.

문제 설명

주어진 배열에서 가장 길게 증가하는 부분 수열(LIS, Longest Increasing Subsequence)을 찾는 문제입니다. 부분 수열이란, 원래 배열의 원소를 순서대로 선택해서 만든 배열이며, 원래 배열의 순서를 유지해야 합니다. 증가 수열이란 원소들이 모두 오름차순으로 정렬되어 있는 수열을 의미합니다.

예제

    입력: [10, 22, 9, 33, 21, 50, 41, 60, 80]
    출력: 6
    설명: 가장 길게 증가하는 부분 수열의 예는 [10, 22, 33, 50, 60, 80]입니다.
    

문제 풀이 접근법

이 문제를 해결하기 위한 기본적인 접근법은 동적 프로그래밍(DP)을 사용하는 것입니다. DP는 문제를 작은 부분 문제로 나누어 푸는 방법이며, 이전의 계산 결과를 저장하여 같은 계산을 반복하지 않도록 합니다.

1단계: DP 테이블 초기화

먼저 DP 테이블을 초기화합니다. 입력 배열의 각 인덱스에서 시작할 때의 LIS 길이를 저장할 배열을 생성합니다. 초기에는 각 원소가 자기 자신으로 이루어진 부분 수열이 최대이므로 모든 값은 1로 설정합니다.

2단계: DP 테이블 업데이트

이제 입력 배열의 모든 원소에 대해 서로 비교합니다. 각 원소 arr[i]에 대해, 그보다 앞에 있는 모든 원소 arr[j] (j < i)를 확인하여 다음 조건을 평가합니다:

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

여기서 dp[i]arr[i]를 포함하는 LIS의 길이이며, dp[j] + 1arr[j] 다음에 arr[i]를 추가한 경우의 길이를 나타냅니다.

3단계: 결과 도출

모든 원소에 대한 처리가 끝난 후, dp 배열에서 가장 큰 값을 찾아 LIS의 길이를 결과로 반환합니다.

코틀린 구현

이제 직접 코틀린으로 구현해보겠습니다. 아래는 앞서 설명한 알고리즘을 따르는 코드입니다.


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")
}
    

복잡도 분석

결과적으로, 이 알고리즘의 시간 복잡도는 O(n²)입니다. 이는 중첩된 반복문이 n에 대해 실행되기 때문입니다. 공간 복잡도는 O(n)으로 DP 배열을 저장하기 위한 공간이 필요합니다.

최적화 방법

만약 더 높은 성능이 요구된다면, 이진 탐색(binary search)을 활용하여 LIS 문제를 O(n log n)으로 해결할 수 있습니다. 이 방법은 다음과 같은 방식으로 구현됩니다:


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
}
    

여기서는 tails 배열이 LIS의 가능한 마지막 요소들을 저장합니다. 이진 탐색을 통해 적절한 위치를 찾아 삽입하거나 교체합니다.

마무리

이번 글에서는 코틀린을 이용한 가장 길게 증가하는 부분 수열 문제에 대해 알아보았습니다. 문제의 접근 방식과 구현 방법을 자세히 설명드렸고, 추가적으로 최적화 방법도 다뤘습니다. 이러한 문제들은 코딩 테스트에서 자주 출제되므로, 반복적인 연습이 중요합니다. 앞으로도 다양한 알고리즘 문제를 함께 나누도록 하겠습니다. 궁금한 점이 있다면 댓글로 질문해 주세요!

코틀린 코딩테스트 강좌, ‘좋은 수’ 구하기

안녕하세요! 이번 시간에는 코틀린을 이용한 코딩 테스트 문제 중 하나인 ‘좋은 수’를 구하는 문제를 해결해 보겠습니다. 이 글에서는 문제 설명, 접근 방식, 알고리즘 설계, 그리고 실제 코드를 단계별로 설명할 예정입니다. 또한 최적화와 함께 다양한 예제도 다룰 예정이니 끝까지 함께 해주세요!

문제 설명

‘좋은 수’는 주어진 정수 리스트에서 서로 다른 수(A, B, C, …)를 선택하여 A + B + C = 0이 되는 경우를 찾는 문제입니다. 예를 들어, 다음과 같은 배열이 주어졌다고 가정합시다.

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

위 배열에서 합이 0이 되는 수의 조합은 (-1, 0, 1)과 (-1, -1, 2)입니다. 이러한 조합을 모두 찾아서 반환하여야 합니다.

문제 접근 방식

이 문제를 해결하기 위해서는 몇 가지 기본적인 접근 방식을 고려해야 합니다.

  1. 정렬: 배열을 정렬함으로써 같은 수를 효과적으로 다룰 수 있고, 두 포인터 기법이나 이진 탐색 등의 알고리즘을 적용할 수 있습니다.
  2. 중복 제거: 같은 숫자를 여러 번 더하지 않도록 처리해야 합니다.
  3. 포인터 기법: 정렬된 리스트를 기반으로 두 개의 포인터를 사용하는 방법으로, 각 조합을 효율적으로 확인할 수 있습니다.

알고리즘 설계

알고리즘은 다음과 같은 단계로 이루어집니다.

  1. 입력 배열을 정렬합니다.
  2. 그 다음, 각 요소에 대해 두 개의 포인터를 사용하여 합이 0이 되는지 검사합니다.
  3. 결과 리스트에 추가하는 과정에서 중복을 피하도록 합니다.

코드 구현

이제 코드를 구현해 보겠습니다. 코틀린으로 다음과 같이 작성합니다:


        fun threeSum(nums: IntArray): List> {
            nums.sort() // 배열 정렬
            val res = mutableListOf>()
            for (i in nums.indices) {
                if (i > 0 && nums[i] == nums[i - 1]) continue // 중복 체크
                var left = i + 1
                var right = nums.size - 1
                while (left < right) {
                    val sum = nums[i] + nums[left] + nums[right]
                    when {
                        sum < 0 -> left++      // 합이 작으면 왼쪽 포인터 이동
                        sum > 0 -> right--     // 합이 크면 오른쪽 포인터 이동
                        else -> {
                            res.add(listOf(nums[i], nums[left], nums[right])) // 합이 0인 경우 추가
                            while (left < right && nums[left] == nums[left + 1]) left++ // 중복 체크
                            while (left < right && nums[right] == nums[right - 1]) right-- // 중복 체크
                            left++
                            right--
                        }
                    }
                }
            }
            return res
        }
        

코드 분석

코드의 각 부분을 분석해 보겠습니다.

  1. 배열 정렬:  `nums.sort()`를 통해 배열을 오름차순으로 정렬합니다. 이는 두 포인터 기법을 적용하기 위한 필수 단계입니다.
  2. 메인 루프: 인덱스를 기준으로 반복하며 각 요소를 선택합니다. 중복이 있을 경우에는 넘어갑니다.
  3. 두 포인터: 선택된 요소를 기반으로 왼쪽과 오른쪽 포인터를 설정합니다. 이들 포인터를 이동하며 세 개의 요소의 합을 계산하고 비교합니다.

최적화 및 시간 복잡도

이 알고리즘의 시간 복잡도는 O(n^2)입니다. 외부 루프가 n번 반복되고, 내부 while 루프가 최대 n번 반복되기 때문입니다. 이로 인해 리스트의 크기가 커질수록 시간 복잡도는 급격히 증가할 수 있으므로 효율적인 처리가 필요합니다.

공간 복잡도는 O(1)으로, 입력 외의 추가적인 공간을 사용하지 않습니다. 다만, 결과 리스트를 저장하는 데 필요한 공간이 사용됩니다.

예제 및 실행 결과

예제 입력과 결과를 통해 이해를 돕겠습니다.


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

결론

본 강좌에서는 코틀린을 사용하여 ‘좋은 수’를 구하는 알고리즘을 설계하고 구현하는 과정을 살펴보았습니다. 정렬, 중복 체크 및 두 포인터 기법을 활용함으로써 문제를 효율적으로 해결할 수 있었습니다. 이와 같은 문제를 풀며 알고리즘적 사고와 코딩 능력을 향상시키는 데에 도움이 되길 바랍니다.

앞으로도 다양한 알고리즘 문제를 통해 여러 해법을 탐구해보는 시간을 가져보세요!

코틀린 코딩테스트 강좌, K번째 수 구하기

안녕하세요! 이번 강좌에서는 코틀린을 활용하여 K번째 수를 구하는 알고리즘 문제에 대해 자세히 알아보겠습니다. K번째 수를 찾는 문제는 데이터 정렬 및 검색 알고리즘의 기초를 다지는 데 유용한 문제입니다. 이 글에서는 문제의 정의, 접근 방법, 코딩 구현 및 최적화 기법까지 폭넓게 다루겠습니다.

문제 정의

문제 설명은 다음과 같습니다:

자연수로 이루어진 배열이 주어지고, 숫자 K가 주어질 때, 배열에서 K번째로 작은 수를 구하시오.

  • 입력: 배열 {5, 3, 8, 1, 2}와 K=3
  • 출력: 3

문제 접근 방법

이 문제를 해결하기 위해서는 다음과 같은 접근 방식을 사용할 수 있습니다:

  1. 배열을 정렬한 후, K-1 인덱스에 위치한 값을 반환
  2. 힙(heap) 자료구조를 사용하여 K번째 수까지 효율적으로 찾기
  3. Quickselect 알고리즘을 사용하여 평균 O(N) 시간 복잡도로 K번째 수를 찾기

1. 배열 정렬

배열을 정렬한 후, K-1 인덱스를 찾는 방법은 가장 직관적이며 이해하기 쉽습니다. 하지만 최악의 경우 O(N log N)의 시간 복잡도를 가지기 때문에, 더 효율적인 방법을 찾아야 합니다.

2. 힙을 활용한 방법

힙을 사용하여 K번째 수를 찾는 방법은 K개의 최소 요소를 힙으로 저장한 후, 힙의 최대값을 찾아 반환하는 방식입니다. 이 접근법은 O(N log K)의 시간 복잡도를 가집니다.

3. Quickselect

Quickselect 알고리즘은 QuickSort의 파티션 기법을 변형하여 원하는 K번째 수를 찾는 방법입니다. 이 알고리즘은 평균적으로 O(N)의 시간 복잡도를 가집니다.

코드 구현

이제 문제를 해결하기 위한 코드를 작성해 보겠습니다. 이번 예제에서는 배열을 정렬하여 K번째 수를 찾는 가장 직관적인 방법을 사용합니다.

K번째 수 찾기 코드


    fun findKthNumber(arr: IntArray, k: Int): Int {
        // 배열 정렬
        val sortedArray = arr.sortedArray()
        // K번째 수 반환 (K는 1부터 시작하므로 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("K번째 수: $result") // 출력: K번째 수: 3
    }
    

예제 및 설명

위 코드에서는 다음 단계를 따릅니다:

  1. findKthNumber 함수를 정의하고 정수 배열과 K를 매개변수로 받습니다.
  2. 배열을 정렬한 후, K-1 인덱스의 값을 반환합니다.
  3. main 함수를 실행하여 결과를 출력합니다.

최적화 및 기타 접근 방법

위의 해결 방법들은 기초적이며, 실제 코딩 테스트에서는 더 많은 문제를 다루어야 할 수 있습니다. K번째 수와 같이 시간 복잡도에 민감한 문제에 대해 몇 가지 추가 방법을 논의해 보겠습니다.

힙을 이용한 방법


    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() // K개를 초과할 경우 최대값 제거
            }
        }
        return minHeap.poll() // K번째 수 반환
    }
    

Quickselect 알고리즘


    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)
    }
    

결론

이번 강좌에서는 K번째 수 구하기 문제를 다양한 방법으로 해결해보았습니다. 배열 정렬을 통한 직관적인 방법부터, 힙, Quickselect 알고리즘까지 여러 접근 방법을 학습하여 알고리즘의 깊은 이해를 도왔습니다. 이러한 문제들은 코딩 테스트에서 자주 출제되므로, 여러가지 방법을 시도해보시기 바랍니다.

추가적으로 K번째 수 구하기 문제는 배열의 정렬과 탐색 알고리즘의 기초이기 때문에, 이러한 기법들을 잘 숙지하고 있다면 다양한 문제에서 유용하게 활용할 수 있습니다. 코드 구현 및 시간 복잡도를 고려하여 효율적인 접근 방법을 선택하시기 바랍니다.

참고 자료

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