덩치 등수 매기기 문제는 주어진 사람들의 키와 몸무게를 비교하여 각자의 상대적인 순위를 구하는 알고리즘 문제입니다. 이번 글에서는 문제를 두 가지 방식으로 해결하며, 효율성을 개선하기 위해 StringBuilder를 활용한 방식을 소개합니다.
문제 설명
덩치 비교 기준
사람의 덩치는 키와 몸무게 두 가지 값으로 표현됩니다. 예를 들어 (x, y)는 한 사람의 몸무게 x와 키 y를 나타냅니다. 두 사람 A와 B의 덩치 (x, y)와 (p, q)를 비교할 때:
- A의 덩치가 더 크다면: x > p 그리고 y > q를 만족해야 합니다.
- 두 값 중 하나라도 크거나 같지 않으면 A와 B의 덩치는 비교할 수 없습니다.
등수 계산
주어진 N명의 사람들에 대해:
- 자신보다 덩치가 더 큰 사람의 수를 구합니다.
- 등수는 이 수에 1을 더한 값입니다.
입력과 출력
입력 예제
5
55 185
58 183
88 186
60 175
46 155
출력 예제
2 2 1 2 5
- (55, 185)보다 큰 덩치를 가진 사람은 1명 → 등수: 2
- (58, 183)보다 큰 덩치를 가진 사람은 1명 → 등수: 2
- (88, 186)보다 큰 덩치를 가진 사람은 없음 → 등수: 1
- (60, 175)보다 큰 덩치를 가진 사람은 1명 → 등수: 2
- (46, 155)보다 큰 덩치를 가진 사람은 4명 → 등수: 5
첫 번째 풀이: 리스트를 이용한 방식
처음에는 각 사람의 덩치와 그보다 큰 덩치의 사람들을 리스트에 저장하여 결과를 확인했습니다.
fun main() {
val br = System.`in`.bufferedReader()
val bw = System.out.bufferedWriter()
val n = br.readLine().toInt()
val weightHeightPairs = mutableListOf<Pair<Int, Int>>()
repeat(n) {
val (weight, height) = br.readLine().split(" ").map { it.toInt() }
weightHeightPairs.add(Pair(weight, height))
}
val results = mutableListOf<Pair<List<Int>, List<List<Int>>>>()
for (i in weightHeightPairs.indices) {
val currentPair = weightHeightPairs[i]
val filteredPairs = weightHeightPairs.filterIndexed { index, otherPair ->
index != i && otherPair.first > currentPair.first && otherPair.second > currentPair.second
}
results.add(currentPair.toList() to filteredPairs.map { it.toList() })
}
bw.write("$weightHeightPairs\n") // 원본 리스트 출력
bw.write("결과: ${results.joinToString(separator = "\n") { "${it.first} -> ${it.second}" }}\n")
bw.flush()
bw.close()
}
출력 예제
[(55, 185), (58, 183), (88, 186), (60, 175), (46, 155)]
결과: [55, 185] -> [[88, 186]]
[58, 183] -> [[88, 186]]
[88, 186] -> []
[60, 175] -> [[88, 186]]
[46, 155] -> [[55, 185], [58, 183], [88, 186], [60, 175]]
장점
- 중간 데이터를 확인하기 쉽습니다.
- 디버깅에 용이하여 알고리즘의 흐름을 이해하기 좋습니다.
단점
- 중간 데이터를 저장하기 위해 추가 메모리가 필요합니다.
- 결과를 출력할 때 joinToString을 사용하여 리스트를 문자열로 변환해야 하므로, 성능이 다소 저하될 수 있습니다.
개선된 풀이: StringBuilder 활용
위 풀이의 단점을 개선하기 위해 StringBuilder를 사용하여 문자열을 바로 생성하는 방식으로 변경했습니다.
fun main() {
val br = System.`in`.bufferedReader()
val bw = System.out.bufferedWriter()
val n = br.readLine().toInt()
val weightHeightPairs = mutableListOf<Pair<Int, Int>>()
repeat(n) {
val (weight, height) = br.readLine().split(" ").map { it.toInt() }
weightHeightPairs.add(Pair(weight, height))
}
val resultBuilder = StringBuilder()
for (i in weightHeightPairs.indices) {
val currentPair = weightHeightPairs[i]
val filteredPairs = weightHeightPairs.filterIndexed { index, otherPair ->
index != i && otherPair.first > currentPair.first && otherPair.second > currentPair.second
}
resultBuilder.append(filteredPairs.size + 1).append(" ")
}
bw.write(resultBuilder.toString().trim())
bw.flush()
bw.close()
}
출력 예제
2 2 1 2 5
개선 사항
- 중간 데이터 제거:
- 리스트 대신 문자열을 바로 생성하므로 메모리 사용량이 줄어듭니다.
- 문자열 변환 작업 최소화:
- 최종 출력 시 joinToString 없이 바로 결과를 생성합니다.
- 성능 향상:
- 대규모 데이터에서 더 효율적으로 동작합니다.
StringBuilder의 역할
StringBuilder란?
StringBuilder는 문자열을 효율적으로 생성, 수정할 수 있도록 도와주는 클래스입니다. 일반적으로 문자열 연결(+) 작업은 새로운 문자열 객체를 생성하므로 비효율적입니다. 반면, StringBuilder는 내부 버퍼를 사용하여 이러한 작업을 최적화합니다.
사용 이유
- 메모리 절약: 문자열 객체를 계속 생성하지 않으므로 메모리 사용량이 감소합니다.
- 성능 향상: 문자열 연결 및 수정 작업에서 빠른 속도를 제공합니다.
두 방식 비교
특징리스트 사용 방식StringBuilder 사용 방식
메모리 사용량 | 추가 리스트 저장 공간 필요 | 추가 메모리 사용 없음 |
문자열 생성 | joinToString를 사용해 리스트를 문자열로 변환 | 문자열을 바로 생성 |
디버깅 용이성 | 중간 데이터를 저장하므로 디버깅이 쉬움 | 중간 데이터를 저장하지 않으므로 디버깅이 어려움 |
적합한 상황 | 중간 데이터를 자주 참조하거나 확인해야 할 때 사용 | 대규모 데이터 처리나 최종 출력이 필요한 경우 사용 |
결론
- 리스트 사용 방식은 디버깅이나 중간 결과를 확인해야 할 때 유용합니다.
- StringBuilder 사용 방식은 성능과 메모리 효율성을 우선시할 때 적합합니다.
문제: https://www.acmicpc.net/problem/7568
반응형
'Computer Science > Algorithm' 카테고리의 다른 글
[Algorithm/Kotlin] 에라토스테네스의 체(Sieve of Eratosthenes) 쉽게 이해하기 (0) | 2025.01.20 |
---|---|
[Algorithm/Kotlin] 백준 10816번 숫자 카드 2: 시간 복잡도를 계산해 시간초과 해결하기 (0) | 2025.01.16 |
[Algorithm/Kotlin] 백준 2164번 카드2: Queue를 활용한 풀이법 (0) | 2025.01.06 |
[Algorithm/Kotlin] 백준 2839번 설탕 배달 (0) | 2025.01.06 |
[Algorithm/Kotlin] 백준 2562번 최댓값: withIndex 문법을 활용한 풀이법 (4) | 2024.12.10 |