코딩테스트/Baekjoon
[백준] 17298번: 오큰수/ Java 11
soooy0
2025. 6. 10. 16:18
- 실패, 정답보고 이해하고 풀이
시간 제한메모리 제한제출정답맞힌 사람정답 비율
1 초 | 512 MB | 104892 | 39169 | 27275 | 35.324% |
문제
크기가 N인 수열 A = A1, A2, ..., AN이 있다. 수열의 각 원소 Ai에 대해서 오큰수 NGE(i)를 구하려고 한다. Ai의 오큰수는 오른쪽에 있으면서 Ai보다 큰 수 중에서 가장 왼쪽에 있는 수를 의미한다. 그러한 수가 없는 경우에 오큰수는 -1이다.
예를 들어, A = [3, 5, 2, 7]인 경우 NGE(1) = 5, NGE(2) = 7, NGE(3) = 7, NGE(4) = -1이다. A = [9, 5, 4, 8]인 경우에는 NGE(1) = -1, NGE(2) = 8, NGE(3) = 8, NGE(4) = -1이다.
입력
첫째 줄에 수열 A의 크기 N (1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄에 수열 A의 원소 A1, A2, ..., AN (1 ≤ Ai ≤ 1,000,000)이 주어진다.
출력
총 N개의 수 NGE(1), NGE(2), ..., NGE(N)을 공백으로 구분해 출력한다.
나의 답안
/*
* :::::: 문제 ::::::
* - 크기가 N인 수열이 존재
* - 각 원소에 대해 오큰수 NGE 구하기
* - 오큰수는 오른쪽에 있으면서 A보다 큰 수 중에서 가장 왼쪽에 있는 수를 의미한다.
* - 그런 수가 없으면 -1
\*
* :::::: 출력 ::::::
* - 총 N개의 수 NGE(1) ... 을 공백으로 구분하여 출력
\*
* :::::: 구현 ::::::
\*
* */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Stack;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Stack<Integer> stack = new Stack<Integer>();
// 수열의 개수 N개
int T = Integer.parseInt(br.readLine());
// T개의 배열 생성
int[] seq = new int[T];
// 수열 공백을 기준으로 나누기
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
// 수열의 개수 N 개만큼 반복
// 이중 for 문을 사용하면 시간초과, 사용 X
for(int i = 0; i < T; i++){
// 각 원소를 배열에 저장
seq[i] = Integer.parseInt(st.nextToken());
}
/*
* 스택이 비어있지 않으면서 AND 현재 원소가 스택의 맨 위 원소가 가리키는 원소보다 큰 경우
* 해당 조건을 만족할 떄까지 stack 의 원소를 pop 하면서
* 해당 인덱스의 값을 현재 원소로 바꿔준다.*/
for(int i = 0; i < T; i++){
while(!stack.isEmpty() && seq[stack.peek()] < seq[i]){
seq[stack.pop()] = seq[i];
}
stack.push(i);
}
/*
* 스택의 모든 원소를 pop 하면서 해당 인덱스의 value 를 -1로 초기화*/
while(!stack.isEmpty()){
seq[stack.pop()] = -1;
}
StringBuilder sb = new StringBuilder();
for(int i = 0; i < T; i++){
sb.append(seq[i]).append(' ');
}
System.out.println(sb);
}
}
- 오큰수는 오른쪽에 있으면서 Ai보다 큰 수 중에서 가장 왼쪽에 있는 수를 의미하므로, 3, 5, 2, 7에서 5를 찾는 꼴을 생각해보면.. 가장 바깥에(오른쪽)에 있는 수 7부터 3과 비교를 진행하면 되고, 가장 마지막에 남는 5를 출력하면 됨
- 이러한 형태는 LIFO 마지막에 들어간 수부터 꺼내는 스택 구조가 되므로 스택으로 풀이하면 된다.
피드백
✅ 1. 이 문제를 왜 스택으로 풀어야 하는가? (왜 LIFO 구조를 써야 할까?) 생각해보기
✔️ 핵심: 앞에서부터 진행하면서, "이전에 본 값들"을 저장하고 빠르게 비교하기 위해서.
- 오큰수는 "현재 값의 오른쪽"에서 자기보다 큰 수 중 가장 가까운 것을 찾아야 함.
- 만약 이중 for문으로 풀면 시간복잡도는 O(N^2) → 시간초과.
- 스택을 사용하면 한 번 순회만으로 해결 가능 → O(N).
✔️ 왜 LIFO가 유리하냐?
- 우리는 최근에 본 인덱스부터 검사해야 함.
- 스택은 최근에 저장한 값을 맨 위에 쌓기 때문에,
"최근값부터 비교 → 현재값이 더 크면 pop → 그 값의 오큰수 확정" - 즉, 가장 최근 인덱스부터 빠르게 처리할 수 있는 자료구조가 LIFO인 스택이라 적합함.
✅ 2. 배열을 애초에 -1로 초기화하는 것, 괜찮을까?
- Arrays.fill(배열,채울값) Arrays.fill(배열,시작인덱스,끝인덱스,채울값) 메서드
- 처음부터 Arrays.fill(seq, -1); 로 초기화해두면,
- 오큰수를 못 찾은 값은 굳이 마지막에 따로 while문으로 -1 처리 안 해도 됨.
Arrays.fill(seq, -1);
이렇게 하면 코드도 더 깔끔해지고, 가독성도 좋아짐.
💡 Tip: 코테에서는 정답 배열을 따로 만들면서 -1로 초기화하는 게 더 좋은 습관
✅ 3. 가급적이면 주어진 input값은 수정하지 말고 정답 배열 따로 쓰기
- 주어진 seq를 그대로 유지하고, 정답을 result[] 같은 배열에 따로 저장하면:
- 원본 유지 → 코드 안전성, 재사용성 증가
- 협업 시 → "데이터를 불필요하게 변경하지 말 것"이 좋은 습관
int[] result = new int[T];
Arrays.fill(result, -1); // 기본값 -1
// 스택 로직에서
result[stack.pop()] = seq[i];
✅ 4. 포인터로 풀면 시간초과 -> 이중for문이기 때문
- 포인터 두 개 (i, j → j = i+1부터 끝까지 탐색) → 이중 for문 → O(N^2)
- N이 1,000,000이라면 절대 못 씀
- 스택은 한 번의 순회 (O(N)) 로 문제 해결 가능 → 이게 핵심.
✅ 5. Stack 대신 ArrayDeque 쓰기