코틀린 코딩테스트 강좌, 소수 & 팰린드롬 수 중에서 최솟값 찾기

문제 정의

문제: 주어진 범위 내의 모든 소수 중에서 팰린드롬 수인 것들을 찾아 그 중에서 최솟값을 구하라.
소수는 1과 자신만을 약수로 가지는 자연수이고, 팰린드롬 수는 앞에서 읽으나 뒤에서 읽으나 같은 숫자입니다.
입력은 두 개의 정수 A와 B로 주어지며, A 이상 B 이하의 범위입니다.

입력 예시

    입력: 10 100
    

출력 예시

    출력: 101
    

문제 풀이 과정

이 문제를 해결하기 위해 다음과 같은 단계로 접근하겠습니다.

1단계: 소수 판별 함수 구현

소수를 판별하는 기능을 먼저 구현합니다. 소수는 1과 자기 자신 외에는 나누어 떨어지지 않는 수입니다.
일반적으로 소수인지 판단하기 위해서는 2부터 √n까지의 수로 나누어 떨어지는지를 확인하면 됩니다.

    fun isPrime(num: Int): Boolean {
        if (num < 2) return false
        for (i in 2..Math.sqrt(num.toDouble()).toInt()) {
            if (num % i == 0) return false
        }
        return true
    }
    

2단계: 팰린드롬 판별 함수 구현

소수 판별이 끝나면, 이제가지를 구현합니다. 문자열로 변환한 후, 뒤집었을 때 같은지 확인하면 됩니다.

    fun isPalindrome(num: Int): Boolean {
        val str = num.toString()
        return str == str.reversed()
    }
    

3단계: 소수 & 팰린드롬 수 찾기

주어진 범위 A 이상 B 이하에서 모든 정수를 체크하며, 각각의 수가 소수이면서 동시에 팰린드롬 수인지 확인합니다.

    fun findMinPalindromicPrime(A: Int, B: Int): Int? {
        var minValue: Int? = null
        for (num in A..B) {
            if (isPrime(num) && isPalindrome(num)) {
                if (minValue == null || num < minValue) {
                    minValue = num
                }
            }
        }
        return minValue
    }
    

4단계: 메인 함수로 통합

위의 모든 함수를 호출하여 최종적으로 결과를 출력하기 위해 메인 함수를 작성합니다. 사용자의 입력을 받고,
범위 내에서 최솟값을 찾는 기능을 구현합니다.

    fun main() {
        val (A, B) = readLine()!!.split(" ").map { it.toInt() }
        val result = findMinPalindromicPrime(A, B)
        
        if (result != null) {
            println("소수 & 팰린드롬 수의 최솟값: $result")
        } else {
            println("해당 범위 내에 소수 & 팰린드롬 수가 없습니다.")
        }
    }
    

종합

이제까지 구현한 내용을 하나로 종합해 보겠습니다. 전체 코드는 아래와 같습니다.

    fun isPrime(num: Int): Boolean {
        if (num < 2) return false
        for (i in 2..Math.sqrt(num.toDouble()).toInt()) {
            if (num % i == 0) return false
        }
        return true
    }

    fun isPalindrome(num: Int): Boolean {
        val str = num.toString()
        return str == str.reversed()
    }

    fun findMinPalindromicPrime(A: Int, B: Int): Int? {
        var minValue: Int? = null
        for (num in A..B) {
            if (isPrime(num) && isPalindrome(num)) {
                if (minValue == null || num < minValue) {
                    minValue = num
                }
            }
        }
        return minValue
    }

    fun main() {
        val (A, B) = readLine()!!.split(" ").map { it.toInt() }
        val result = findMinPalindromicPrime(A, B)
        
        if (result != null) {
            println("소수 & 팰린드롬 수의 최솟값: $result")
        } else {
            println("해당 범위 내에 소수 & 팰린드롬 수가 없습니다.")
        }
    }
    

결론

이 글에서는 코틀린을 활용하여 소수 직접 판별하는 방법과 팰린드롬을 판별하여,
최솟값을 찾는 과정을 자세히 설명하였습니다. 이러한 문제 해결 방법은 취업 면접을 대상으로 하는 알고리즘 문제가 종종 포함되어
있기 때문에 연습해보시면 좋습니다. 항상 다양한 문제들을 풀어보며 실력을 계속해서 향상시키시기 바랍니다.

코틀린 코딩테스트 강좌, 세일즈맨의 고민

문제 설명

세일즈맨이 여러 도시를 돌며 고객을 방문해야 합니다. 세일즈맨은 각 도시에서 상품을 판매할 수 있으며, 각 도시는 서로 다른 거리에 위치하고 있습니다. 세일즈맨의 목표는 모든 도시를 순회하여 처음 출발한 도시로 돌아오는 최소의 비용을 계산하는 것입니다. 이 문제는 유명한 “외판원 문제(Traveling Salesman Problem, TSP)”로 알려져 있으며, NP-완전 문제 중 하나입니다.

문제 설명을 위한 예시

다음과 같이 4개의 도시가 있다고 가정해 보겠습니다:

  • 도시 A – (0, 0)
  • 도시 B – (1, 2)
  • 도시 C – (4, 3)
  • 도시 D – (7, 7)

이 경우 세일즈맨은 A, B, C, D 도시를 모두 방문하고 다시 A로 돌아오는 최소 경로의 거리를 찾아야 합니다.

문제 접근 방법

이 문제를 해결하기 위해 여러 접근 방식이 있지만, 주로 사용하는 방법은 동적 프로그래밍(Dynamic Programming)입니다. 동적 프로그래밍을 사용하여 모든 도시를 탐색하는 것을 최적화할 수 있습니다.

1. 비트 마스킹을 이용한 동적 프로그래밍

비트 마스킹을 사용하면 각 도시의 방문 여부를 비트로 표현할 수 있습니다. 예를 들어, 4개의 도시에 대한 비트 표현은 0000(아무 도시도 방문하지 않음)부터 1111(모든 도시를 방문함)까지의 값으로 표현될 수 있습니다. 각 비트의 1은 해당 도시가 방문되었음을 의미합니다.

2. 동적 프로그래밍 상태 정의

DP 테이블을 정의합시다. dp[mask][i]는 방문한 도시를 나타내는 비트 마스크가 mask일 때 도시 i에 도착하기 위한 최소 비용을 의미합니다. 초기 상태는 dp[1<

3. 점화식 정의

점화식은 다음과 같습니다:

dp[mask][i] = min(dp[mask ^ (1 << i)][j] + dist[j][i]) for all j where j is in mask and j != i

이 점화식은 현재 도시 i에서 비트 마스크가 mask인 상태에서 이전에 방문한 도시 j에서 오는 최적 비용을 계산하는 과정입니다.

코드 구현

알고리즘을 코틀린으로 구현해 보겠습니다.

        
        fun main() {
            val cities = arrayOf(
                Pair(0, 0),
                Pair(1, 2),
                Pair(4, 3),
                Pair(7, 7)
            )
            val n = cities.size
            val dist = Array(n) { IntArray(n) }
            
            // 거리 계산
            for (i in 0 until n) {
                for (j in 0 until n) {
                    dist[i][j] = manhattanDistance(cities[i], cities[j])
                }
            }

            // DP 배열 초기화
            val dp = Array(1 shl n) { IntArray(n) { Int.MAX_VALUE } }

            for (i in 0 until n) {
                dp[1 shl i][i] = 0
            }

            // DP 처리
            for (mask in 1 until (1 shl n)) {
                for (i in 0 until n) {
                    if (mask and (1 shl i) == 0) continue
                    for (j in 0 until n) {
                        if (mask and (1 shl j) == 0 || i == j) continue
                        dp[mask][i] = minOf(dp[mask][i], dp[mask xor (1 shl i)][j] + dist[j][i])
                    }
                }
            }

            // 최소 비용 계산
            var minCost = Int.MAX_VALUE
            
            for (i in 0 until n) {
                minCost = minOf(minCost, dp[(1 shl n) - 1][i] + dist[i][0])
            }
            println("최소 비용: $minCost")
        }

        fun manhattanDistance(city1: Pair, city2: Pair): Int {
            return Math.abs(city1.first - city2.first) + Math.abs(city1.second - city2.second)
        }
        
    

코드 설명

위의 코드는 다음과 같은 과정을 거쳐 동적 프로그래밍을 통해 최적 경로의 비용을 계산합니다:

  1. 거리 계산: 두 도시 간의 맨해튼 거리를 계산합니다.
  2. DP 초기화: 모든 도시를 방문하지 않은 상태에서의 초기 DP 배열을 설정합니다.
  3. DP 처리: 비트 마스크와 현재 도시를 기반으로 최적 비용을 계산합니다.
  4. 최소 비용 계산: 모든 도시를 방문한 후 돌아올 때의 최소 비용을 계산합니다.

복잡도 분석

이 알고리즘의 시간 복잡도는 O(n^2 * 2^n)입니다. 이는 n이 작을 때 유효하게 작동하지만, n이 커질수록 성능 저하가 급격히 발생합니다. 따라서, 이 문제는 대규모 입력에서는 비효율적일 수 있어, 다양한 최적화 기법이 필요할 수 있습니다.

결론

“세일즈맨의 고민” 문제는 코딩 테스트에서 자주 등장하는 알고리즘 문제 중 하나입니다. 이 문제를 통해 동적 프로그래밍 및 비트 마스킹의 중요성을 이해하고 실제로 코틀린을 사용하여 해결하는 방법을 배울 수 있습니다. 최적화 및 기타 기법에 대해 깊이 있는 추가 학습을 통해 더 복잡한 문제를 해결할 수 있는 능력을 키우는 것이 좋습니다.

코틀린 코딩테스트 강좌, 선택 정렬

작성자: 조광형 | 날짜: [날짜]

서론

코딩 테스트는 오늘날 많은 기업의 채용 과정에서 중요한 역할을 합니다. 특히, 알고리즘 문제를 해결하는 능력은
이직과 신입 채용 모두에 있어 큰 영향을 미칩니다. 이번 강좌에서는 선택 정렬(Selection Sort) 알고리즘을
코틀린으로 구현하고, 이에 대한 문제를 풀어보겠습니다.

선택 정렬이란?

선택 정렬은 간단한 정렬 알고리즘 중 하나로, 주어진 리스트에서 가장 작은 요소를 찾아 첫 번째 위치와 교환하고,
이후 남은 리스트에서 가장 작은 요소를 찾아 두 번째 위치와 교환하는 방법입니다. 이러한 과정은
리스트가 정렬될 때까지 반복됩니다.

선택 정렬의 동작 원리

  1. 리스트에서 가장 작은 원소를 찾습니다.
  2. 그 원소를 현재 정렬되지 않은 리스트의 맨 앞에 위치한 원소와 교환합니다.
  3. 정렬되지 않은 리스트의 시작을 한 칸 앞으로 이동시킵니다.
  4. 2-3 과정을 리스트의 크기 – 1만큼 반복합니다.

문제 설명

주어진 정수 배열을 오름차순으로 정렬하는 선택 정렬 알고리즘을 작성하세요. 이때 구체적인 요구 사항은 다음과 같습니다:

  • 입력: 정수 배열 (예: [64, 25, 12, 22, 11])
  • 출력: 정렬된 정수 배열 (예: [11, 12, 22, 25, 64])

문제 풀이 과정

1단계: 문제 이해

첫 번째 단계는 문제를 완전히 이해하는 것입니다. 선택 정렬 알고리즘이 배열을 어떻게 정렬하는지 이해하고,
입력과 출력을 명확히 하는 것이 중요합니다.

2단계: 알고리즘 설계

다음으로, 선택 정렬의 알고리즘을 설계합니다. 코틀린을 사용하여 이러한 선택 정렬 알고리즘을 구현할 것입니다.

fun selectionSort(arr: IntArray): IntArray {
    for (i in arr.indices) {
        // 현재 인덱스 i를 가진 원소를 가장 작은 원소로 가정
        var minIndex = i

        // i 다음 인덱스부터 끝까지 반복하면서 최솟값을 찾음
        for (j in (i + 1) until arr.size) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j
            }
        }

        // 가장 작은 원소와 현재 인덱스의 원소를 교환
        if (minIndex != i) {
            val temp = arr[i]
            arr[i] = arr[minIndex]
            arr[minIndex] = temp
        }
    }
    return arr
}

3단계: 코드 구현

위의 알고리즘을 바탕으로 코틀린 코드를 작성합니다. 아래는 해당 알고리즘의 구현 예입니다:

fun main() {
    val array = intArrayOf(64, 25, 12, 22, 11)
    val sortedArray = selectionSort(array)

    println("정렬된 배열: ${sortedArray.joinToString(", ") { it.toString() }}")
}

4단계: 코드 테스트

작성한 알고리즘을 테스트하여 잘 작동하는지 확인합니다. 배열을 가지고 실제로 프로그램을 실행하여
결과를 확인합니다.

fun testSelectionSort() {
    val testCases = arrayOf(
        intArrayOf(64, 25, 12, 22, 11) to intArrayOf(11, 12, 22, 25, 64),
        intArrayOf(5, 4, 3, 2, 1) to intArrayOf(1, 2, 3, 4, 5),
        intArrayOf(1, 2, 3, 4, 5) to intArrayOf(1, 2, 3, 4, 5),
        intArrayOf(-1, -5, 3, 0) to intArrayOf(-5, -1, 0, 3)
    )

    for ((input, expected) in testCases) {
        val result = selectionSort(input)
        assert(result.contentEquals(expected)) {
            "Test failed for input: ${input.joinToString(", ")}. Expected: ${expected.joinToString(", ")}, but got: ${result.joinToString(", ")}}"
        }
    }
    println("모든 테스트 통과!")
}

fun main() {
    testSelectionSort()
}

5단계: 최적화 및 복잡도

선택 정렬의 시간복잡도는 O(n^2)이며 최악의 경우와 최선의 경우 모두 동일합니다. 이는 알고리즘의
비효율성으로 인해 대량의 데이터에서 성능 저하가 발생할 수 있습니다. 그러나 구현이 간단하고
데이터 양이 적을 때는 효과적으로 사용할 수 있습니다.

결론

이번 강좌에서는 선택 정렬의 이론과 코틀린으로의 구현 방법을 살펴보았습니다. 간단한 문제를 통해 알고리즘
문제를 푸는 과정을 이해하고, 실제 사용 예시를 통해 알고리즘의 동작 방식을 익힐 수 있었습니다.
다음 강좌에서는 더 효율적인 정렬 알고리즘을 다룰 예정이니 기대해 주세요.

이 글이 도움이 되셨다면, 댓글로 여러분의 생각을 공유해 주세요!

코틀린 코딩테스트 강좌, 세그먼트 트리

세그먼트 트리(Segment Tree)는 배열의 구간 합, 구간 최소값, 구간 최대값 등을 효과적으로 구할 수 있는 자료구조입니다. 이 자료구조는 특히 구간 쿼리와 업데이트가 빈번하게 이루어지는 상황에서 빠른 성능을 발휘합니다. 이 강좌에서는 세그먼트 트리의 기본적인 구조와 구현 방법, 그리고 실제 코딩테스트에서 활용할 수 있는 문제를 다루겠습니다.

세그먼트 트리란?

세그먼트 트리는 배열의 특정 구간에 대한 정보를 저장하기 위한 이진 트리 형태의 자료구조입니다. 각 노드는 특정 구간의 정보를 저장하고, 이를 통해 빠른 쿼리와 업데이트가 가능합니다. 주로 다음과 같은 두 가지 작업을 효율적으로 수행하는 데 사용됩니다.

  • 구간 쿼리(Query): 특정 구간의 합, 최소값, 최대값 등 계산
  • 업데이트(Update): 특정 위치의 값을 변경

세그먼트 트리 구조

세그먼트 트리는 완전 이진 트리로 구성되며, 일반적으로 다음과 같은 방식으로 배열을 구성합니다. 이를 위해, 세그먼트 트리의 각 노드는 부모와 자식 간의 관계를 통해 정의됩니다. 배열의 노드는 다음과 같은 규칙을 따릅니다:

  • 부모 노드는 인덱스 i의 왼쪽 자식이 2*i, 오른쪽 자식이 2*i + 1
  • 리프 노드는 주어진 배열의 각 원소와 상대하는 인덱스에 저장됩니다.

세그먼트 트리 구현

코틀린을 이용한 세그먼트 트리의 구현을 살펴보겠습니다. 먼저, 기본적인 세그먼트 트리 클래스를 정의하고, 초기화, 업데이트 및 쿼리 기능을 구현해 보겠습니다.

1. 세그먼트 트리 클래스 정의


class SegmentTree(private val array: IntArray) {
    private val size: Int = array.size
    private val tree: IntArray = IntArray(4 * size)
    
    init {
        build(0, 0, size - 1)
    }

    private fun build(node: Int, start: Int, end: Int) {
        if (start == end) {
            tree[node] = array[start]
        } else {
            val mid = (start + end) / 2
            build(2 * node + 1, start, mid)
            build(2 * node + 2, mid + 1, end)
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2]
        }
    }

    fun update(index: Int, value: Int) {
        update(0, 0, size - 1, index, value)
    }

    private fun update(node: Int, start: Int, end: Int, index: Int, value: Int) {
        if (start == end) {
            array[index] = value
            tree[node] = value
        } else {
            val mid = (start + end) / 2
            if (index in start..mid) {
                update(2 * node + 1, start, mid, index, value)
            } else {
                update(2 * node + 2, mid + 1, end, index, value)
            }
            tree[node] = tree[2 * node + 1] + tree[2 * node + 2]
        }
    }
    
    fun query(L: Int, R: Int): Int {
        return query(0, 0, size - 1, L, R)
    }

    private fun query(node: Int, start: Int, end: Int, L: Int, R: Int): Int {
        if (R < start || end < L) return 0 // 쿼리 범위와 노드 범위가 겹치지 않음
        if (L <= start && end <= R) return tree[node] // 노드 범위가 쿼리 범위에 포함됨
        val mid = (start + end) / 2
        val leftSum = query(2 * node + 1, start, mid, L, R)
        val rightSum = query(2 * node + 2, mid + 1, end, L, R)
        return leftSum + rightSum
    }
}

2. 사용 예시

위에서 정의한 세그먼트 트리를 사용하여 구간의 합을 구하고 업데이트를 수행하는 예시를 살펴보겠습니다.


fun main() {
    val array = intArrayOf(1, 3, 5, 7, 9, 11)
    val segmentTree = SegmentTree(array)

    println("구간 [1, 3]의 합: ${segmentTree.query(1, 3)}") // 3 + 5 + 7 = 15
    segmentTree.update(1, 10) // array[1]를 10으로 업데이트
    println("업데이트 후 구간 [1, 3]의 합: ${segmentTree.query(1, 3)}") // 10 + 5 + 7 = 22
}

코딩테스트 문제

이제 실제 코딩테스트에서 자주 다뤄지는 문제를 함께 풀어보겠습니다. 아래 문제는 세그먼트 트리를 활용한 응용 문제입니다.

문제: 구간 합과 업데이트

주어진 배열의 특정 구간에 대한 합을 구하는 문제입니다. 추가로 특정 인덱스의 값을 업데이트할 수 있습니다.

문제 설명

- N개의 정수로 이루어진 배열이 주어집니다.
- 이전의 쿼리 결과로부터 Q번 만큼 다음의 작업을 수행합니다.
    1. "1 L R" 쿼리는 배열의 인덱스 L부터 R까지의 합을 요청합니다.
    2. "2 X Y" 쿼리는 인덱스 X의 값을 Y로 업데이트합니다.

입력 형식

- 첫 번째 줄에는 배열의 크기 N과 쿼리의 개수 Q가 주어집니다.
- 두 번째 줄에는 N개의 정수가 주어집니다.
- 그 다음 Q개의 줄에는 쿼리가 주어집니다.

출력 형식

- 각 "1 L R" 쿼리에 대해 구간의 합을 출력합니다.

입력 예시

5 3
1 2 3 4 5
1 1 3
2 1 10
1 1 3

출력 예시

6

문제 풀이

해당 문제를 해결하기 위해 세그먼트 트리를 사용하여 효율적으로 구간 합 쿼리와 업데이트 연산을 실행하도록 구현합니다. 입력으로부터 데이터를 받고 세그먼트 트리를 초기화한 후 쿼리를 처리하는 구조를 가집니다.

구현


fun main() {
    val (n, q) = readLine()!!.split(" ").map { it.toInt() }
    val array = readLine()!!.split(" ").map { it.toInt() }.toIntArray()
    val segmentTree = SegmentTree(array)

    repeat(q) {
        val query = readLine()!!.split(" ").map { it.toInt() }
        if (query[0] == 1) {
            // 구간 합 쿼리
            val sum = segmentTree.query(query[1] - 1, query[2] - 1)
            println(sum)
        } else if (query[0] == 2) {
            // 업데이트 쿼리
            segmentTree.update(query[1] - 1, query[2])
        }
    }
}

결론

이번 강좌에서는 세그먼트 트리의 기본 개념 및 구현 방법을 익혔습니다. 세그먼트 트리는 효율적인 구간 쿼리와 업데이트 구현에 매우 유용한 자료구조이며, 코딩테스트 문제에 자주 출제됩니다. 이번 예제를 통해 세그먼트 트리의 응용을 익혔기를 바라며, 이를 통해 향후 다양한 문제를 해결하는 데 도움이 되길 바랍니다.

코딩테스트 준비 시에는 이러한 다양한 자료구조와 알고리즘을 체계적으로 학습하여 응용 능력을 키우는 것이 중요합니다. 앞으로도 다양한 자료구조와 알고리즘을 통해 더 나은 문제 해결 능력을 갖추시기 바랍니다.

코틀린 코딩테스트 강좌, 선분의 교차 여부 구하기

프로그래밍 대회나 코딩 테스트에서 선분의 교차 여부를 판단하는 문제는 자주 출제되는 주제 중 하나입니다. 이 강좌에서는 Kotlin 언어를 사용하여 선분의 교차 여부를 구하는 방법을 알아보겠습니다. 이 과정을 통해 기하학적 기법과 수학적 사고를 기를 수 있습니다. 또한, 다양한 테스트 케이스를 통해 알고리즘의 정확성을 검증할 것입니다.

문제 설명

두 선분이 주어졌을 때, 이 두 선분이 서로 교차하는지 여부를 판단하는 문제입니다. 선분 A는 점 P1(x1, y1)과 점 P2(x2, y2)로 정의되고, 선분 B는 점 Q1(x3, y3)과 점 Q2(x4, y4)로 정의됩니다. 교차할 경우 “YES”를, 교차하지 않을 경우 “NO”를 출력하여야 합니다.

입력 형식

  • 첫 번째 줄에 선분 A의 두 점 P1, P2를 나타내는 정수 x1, y1, x2, y2가 공백으로 구분되어 주어진다.
  • 두 번째 줄에 선분 B의 두 점 Q1, Q2를 나타내는 정수 x3, y3, x4, y4가 공백으로 구분되어 주어진다.

출력 형식

교차 여부에 따라 “YES” 또는 “NO”를 출력한다.

문제 접근 방법

선분 교차 여부를 판단하기 위해 다음과 같은 절차를 따릅니다:

  1. 두 선분이 교차하기 위해서는 서로의 방향성을 고려해야 합니다.
  2. 두 선분의 각각의 끝 점과 다른 선분의 두 점을 사용하여 방향을 판단합니다.
  3. 선분의 교차 여부는 구간위치를 통해 판단할 수 있습니다.

구체적으로는 선분 A의 점 P1과 P2가 선분 B의 점 Q1과 Q2를 통해 반시계 방향인지 시계 방향인지 판단할 수 있습니다. 이를 통해 교차 여부를 판별할 수 있습니다.

Kotlin 코드 구현

이제 Kotlin 언어로 구현된 코드를 살펴보겠습니다. 아래 코드는 선분 A와 B의 교차 여부를 판단하는 간단한 예시입니다.


fun main() {
    val (x1, y1, x2, y2) = readLine()!!.split(" ").map { it.toInt() }
    val (x3, y3, x4, y4) = readLine()!!.split(" ").map { it.toInt() }

    if (doIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) {
        println("YES")
    } else {
        println("NO")
    }
}

// 두 점의 방향성을 계산
fun orientation(px: Int, py: Int, qx: Int, qy: Int, rx: Int, ry: Int): Int {
    val value = (qy - py) * (rx - qx) - (qx - px) * (ry - qy)
    return when {
        value == 0 -> 0 // 직선
        value > 0 -> 1  // 반시계 방향
        else -> 2       // 시계 방향
    }
}

// 두 선분이 교차하는지 판단하는 함수
fun doIntersect(x1: Int, y1: Int, x2: Int, y2: Int, x3: Int, y3: Int, x4: Int, y4: Int): Boolean {
    val o1 = orientation(x1, y1, x2, y2, x3, y3)
    val o2 = orientation(x1, y1, x2, y2, x4, y4)
    val o3 = orientation(x3, y3, x4, y4, x1, y1)
    val o4 = orientation(x3, y3, x4, y4, x2, y2)

    // 일반적인 경우
    if (o1 != o2 && o3 != o4) return true

    // 선분이 같은 선 위에 있을 때의 예외 처리
    if (o1 == 0 && onSegment(x1, y1, x3, y3, x2, y2)) return true
    if (o2 == 0 && onSegment(x1, y1, x4, y4, x2, y2)) return true
    if (o3 == 0 && onSegment(x3, y3, x1, y1, x4, y4)) return true
    if (o4 == 0 && onSegment(x3, y3, x2, y2, x4, y4)) return true

    return false
}

// 주어진 점이 선분 위에 있는지 판단하는 함수
fun onSegment(px: Int, py: Int, qx: Int, qy: Int, rx: Int, ry: Int): Boolean {
    return (rx <= maxOf(px, qx) && rx >= minOf(px, qx) &&
            ry <= maxOf(py, qy) && ry >= minOf(py, qy))
}

코드 설명

위의 코드는 선분 A와 B의 교차 여부를 판단하는 과정을 구현한 것입니다.

  • orientation 함수: 이 함수는 주어진 세 점 (P, Q, R)의 방향을 계산합니다. 방향에 따라 값이 0 (직선), 1 (반시계 방향), 2 (시계 방향)로 분류됩니다. 이 값은 교차 여부를 판단하는 데 사용됩니다.
  • doIntersect 함수: 이 함수는 각 선분의 방향성을 계산하고, 일반적인 경우와 예외적인 경우를 판단하여 결과를 반환합니다.
  • onSegment 함수: 주어진 점 R이 선분 PQ 위에 있는지를 판단합니다. 선분의 양 끝 점과 비교하여 R의 위치가 선분 내에 포함되는지를 체크합니다.

테스트 케이스

이제 다양한 테스트 케이스를 통해 코드의 정확성을 확인하겠습니다.

  • 테스트 케이스 1: 0 0 2 2 0 2 2 0결과: YES
  • 테스트 케이스 2: 1 1 10 1 1 2 10 2결과: NO
  • 테스트 케이스 3: 10 0 0 10 0 0 10 10결과: YES
  • 테스트 케이스 4: 1 1 3 3 1 3 3 1결과: YES
  • 테스트 케이스 5: 0 0 0 10 0 5 10 5결과: NO

결론

이번 강좌에서는 선분의 교차 여부를 판단하는 효율적인 알고리즘을 Kotlin으로 구현하였습니다. 기하학적인 개념을 기반으로 한 이 문제는 다양한 상황에서 유용하게 활용될 수 있습니다. 적용 가능한 다양한 알고리즘과 기법을 통해 다양한 문제를 풀어보는 경험이 중요합니다.

코드와 테스트 케이스를 실제로 구현하고 실행해보며 알고리즘의 동작 방식을 명확하게 이해하는 것이 중요합니다. 지속적으로 실습하면 기하학적 문제 해결 능력을 키울 수 있습니다.