🌟 Today's Focus
BOJ 18870 좌표 압축
- 핵심 개념: 정렬, 중복 제거, 값-인덱스 매핑
- 자료구조: ArrayList/TreeSet, HashMap
- 알고리즘: 정렬
📝 Today I Learned
오랜만에 코테 푸는데 정렬이라고 좀 만만하게 봤다가 큰코 다침.
문제 분석 ( 재정의 )
https://www.acmicpc.net/problem/18870
"Xi를 좌표 압축한 결과 X'i의 값은 Xi > Xj를 만족하는 서로 다른 좌표 Xj의 개수와 같아야 한다."라는 문장의 의미를 처음에 이해 못했다;;
막상 까고 보니 "각 숫자보다 작은 서로 다른 숫자들의 개수를 세는 것"이더라...... 외국어 해석하는 줄 알았음.

아무튼 N개의 수를 공백으로 구분해서 던져주면, 그걸 '좌표 압축'해서 뱉어내야 된다는 건데.
처음에 도무지 이 규칙이 눈에 보이지가 않아서 정렬 힌트로 겨우 알아냈음.
좌표 압축이 결국 입력값으로 준 수들 간 크기 비교해서 그 수보다 작은 수가 이 배열 안에 있음 몇 개인지 뱉어내는 거였음.
앞서 해석한 외국어 "각 숫자보다 작은 서로 다른 숫자들의 개수를 세라"가 결국
=> 정렬해서 중복 제거하고 랭킹 매겨라 인 걸로 이해함.
문제 접근 & 풀이
그래서 처음엔
- 원본 배열 저장하고 (나중에 또 써야 하니까 순서 유지하려고)
- 중복 제거(어차피 압축결과는 같아서) & 정렬(순서 매겨야되니까)
- 각 숫자의 압축값(순위) 매핑 -> 해시
- 원본 순서대로 압축값을 해시에서 꺼내서 출력
방식으로 해봄.
틀림
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
int[] original = new int[N];
StringTokenizer st = new StringTokenizer(br.readLine());
// original input[]
for(int i = 0; i < N; i++) {
original[i] = Integer.parseInt(st.nextToken());
}
// remove dupl -> HashSet
HashSet<Integer> set = new HashSet<>();
for (int num: original) {
set.add(num);
}
Integer[] sorted = set.toArray(new Integer[0]);
Arrays.sort(sorted);
HashMap<Integer, Integer> rankMap = new HashMap<>();
for (int i = 0; i < sorted.length; i++);
StringBuilder sb = new StringBuilder();
for (int num : original) {
sb.append(rankMap.get(num)).append(" ");
}
System.out.println(sb);
br.close();
}
}
아는 자료구조 총동원해서 별짓 다함. 결과는 '틀렸습니다' ^___^
HashSet 버림 - 불필요한 자료구조 변환으로 인한 오버헤드
그래도 배열은 포기 못하고 대신 ArrayList + 정렬 방식으로 하니까 성공(1704ms)함.
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
int[] original = new int[N];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
original[i] = Integer.parseInt(st.nextToken());
}
ArrayList<Integer> sorted = new ArrayList<>();
for (int num : original) {
sorted.add(num);
}
Collections.sort(sorted);
HashMap<Integer, Integer> rankMap = new HashMap<>();
int rank = 0;
for (int i = 0; i < sorted.size(); i++) {
if(!rankMap.containsKey(sorted.get(i))) {
rankMap.put(sorted.get(i), rank++);
}
}
StringBuilder sb = new StringBuilder();
for (int num : original) {
sb.append(rankMap.get(num)).append(" ");
}
System.out.println(sb);
br.close();
}
}
아니 근데 어찌어찌 다 풀고 나니까 TreeSet이 정렬도 자동이고 중복도 제거해준다네
쓰면 코드 가독성 확 올라갈 거 같았음.
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
int[] original = new int[N];
StringTokenizer st = new StringTokenizer(br.readLine());
for(int i = 0; i < N; i++) {
original[i] = Integer.parseInt(st.nextToken());
}
TreeSet<Integer> treeSet = new TreeSet<>();
for (int num: original) {
treeSet.add(num);
}
HashMap<Integer, Integer> rankMap = new HashMap<>();
int rank = 0;
for (int num: treeSet) {
rankMap.put(num, rank++);
}
StringBuilder sb = new StringBuilder();
for (int num : original) {
sb.append(rankMap.get(num)).append(" ");
}
System.out.println(sb);
br.close();
}
}
1764ms로 통과함. 근데 왜 시간 차이가 나는지 궁금했음.
같은 시간복잡도(O(N log N))던데.
알아보니 TreeSet은 매 삽입마다 트리 재구성이 필요하다고.
ArrayList는 한 번의 정렬로 캐시 효율성이 높다고 함.

💭 Reflection
TreeSet<Integer> treeSet = new TreeSet<>();
for(int num : original) {
treeSet.add(num);
}
HashMap<Integer, Integer> rankMap = new HashMap<>();
int rank = 0;
for(int num : treeSet) {
rankMap.put(num, rank++);
}
- 문제가 이해되지 않을 때는 구체적인 예제로 차근차근 접근하면 된다. 요즘엔 종이에 냅다 그리거나 쓰면서 하는데 도움이 된다. 처음엔 어려워 보였던 문제 설명도 예제를 통해 이해하니 명확해졌다.
- 같은 시간복잡도(O(N log N))를 가진 알고리즘이라도 실제 수행시간은 다를 수 있다. ArrayList는 한 번에 정렬하는 반면, TreeSet은 매 삽입마다 정렬을 유지해야 해서 조금 더 느렸다.
- 가끔은 코드 한 줄을 줄이는 것보다 의도를 명확히 하는 게 더 중요할 수 있다. TreeSet 버전이 비록 조금 더 느리지만, 코드의 의도가 가장 잘 드러나서 유지보수하기 좋다.
다음에는 이런 자료구조들의 내부 구현을 좀 더 자세히 공부해봐야겠다. 특히 TreeSet이 사용하는 레드-블랙 트리가 어떻게 구현되어있는지 궁금해졌다.
'DSA > 코딩테스트' 카테고리의 다른 글
| LeetCode 2529. Maximum Count of Positive Integer and Negative Integer - (0) | 2025.03.16 |
|---|---|
| 99클럽 코딩테스트 스터디 5기 수료 자세한 후기 (0) | 2025.02.25 |
| 99클럽 코테 스터디 16일차 TIL - Java 프로그래머스 더 맵게 (0) | 2025.02.11 |
| 배열로 시작하는 코딩테스트 첫걸음: 시행착오와 성장의 기록 (1) | 2025.02.04 |
| 그래프 알고리즘 BFS & DFS 정복 feat. 백준 2573번 빙산 찾기, Softeer 장애물 인식 (2) | 2025.02.04 |