작성자: 조광형 | 작성일: 2024년 11월 26일
1. 문제 정의
오큰수는 주어진 수열에서 특정 요소의 오른쪽에 위치하며, 그 요소보다 큰 첫 번째 수를 의미합니다. 만약 그러한 수가 없다면 -1을 반환합니다. 즉, 수열 a[0], a[1], ..., a[n-1]
가 있을 때, 각 a[i]
에 대해 a[j]
(j > i)를 만족하는 가장 작은 j
를 찾는 것이 목표입니다.
예를 들어, 주어진 배열이 [2, 1, 4, 3]
라면, 각 요소의 오큰수는 다음과 같습니다:
- 2의 오큰수는 4입니다.
- 1의 오큰수는 4입니다.
- 4의 오큰수는 -1입니다.
- 3의 오큰수는 -1입니다.
결과적으로 반환되는 오큰수 배열은 [4, 4, -1, -1]
가 됩니다.
2. 문제 접근 방법
이 문제를 해결하기 위해 스택을 사용하는 방법을 고려하겠습니다. 스택은 LIFO(Last In First Out) 구조로, 요소를 삽입하거나 삭제할 때 매우 효율적인 자료구조입니다. 오큰수를 찾는 과정에서 다음의 절차를 따릅니다:
- 빈 스택을 초기화합니다.
- 배열을 왼쪽에서 오른쪽으로 순회합니다.
- 현재 요소
a[i]
를 확인하고: - 스택이 비어있지 않고, 스택의 최상단 요소 인덱스가 가리키고 있는 수가
a[i]
보다 작으면: - 스택의 최상단 인덱스에 대해 오큰수를
a[i]
로 설정합니다. - 스택에서 해당 인덱스를 제거합니다.
- 현재 인덱스를 스택에 추가합니다.
- 배열의 모든 요소를 순회한 후, 스택에 남아있는 인덱스에 대해 오큰수를 -1로 설정합니다.
이 방법은 시간 복잡도 O(n)으로, 각 요소를 한 번만 스택에 추가하고 제거하기 때문입니다. 이는 효율적이며, 결론적으로 큰 입력값에서도 좋은 성능을 발휘할 수 있습니다.
3. 스위프트 코드 구현
이제 위에서 설명한 알고리즘을 스위프트로 구현해 보겠습니다. 아래는 오큰수를 계산하는 스위프트 함수입니다:
import Foundation
func nextGreaterElement(_ nums: [Int]) -> [Int] {
let n = nums.count
var result = Array(repeating: -1, count: n) // 결과 배열 초기화
var stack = [Int]() // 인덱스를 저장할 스택
for i in 0..
이 코드는 먼저 빈 결과 배열과 스택을 초기화한 후, 각 요소에 대해 왼쪽에서 오른쪽으로 탐색하며 오큰수를 찾습니다. 스택의 최상단 요소가 현재 요소보다 작다면, 스택에서 제거하고 오큰수를 설정하는 과정을 반복합니다. 최종적으로 결과 배열을 반환합니다.
4. 테스트 및 검증
작성한 코드를 여러 테스트 케이스로 검증해보겠습니다. 다음은 다양한 입력을 사용한 테스트입니다:
let test1 = [2, 1, 4, 3]
let test2 = [1, 3, 2, 4]
let test3 = [5, 4, 3, 2, 1]
let test4 = [1, 2, 3, 4, 5]
print(nextGreaterElement(test1)) // [4, 4, -1, -1]
print(nextGreaterElement(test2)) // [3, 4, 4, -1]
print(nextGreaterElement(test3)) // [-1, -1, -1, -1, -1]
print(nextGreaterElement(test4)) // [-1, -1, -1, -1, -1]
모든 테스트 케이스에서 예상한 결과와 일치하는 것을 확인할 수 있었습니다. 따라서 구현한 알고리즘이 올바르게 작동함을 알 수 있습니다.
5. 최적화 및 추가 고려사항
오큰수 구하기 문제를 스택을 이용하여 O(n)으로 해결할 수 있다는 점은 매우 유용합니다. 그러나 최적화를 더 고민해볼 여지도 있습니다. 예를 들어, 원형 배열을 응용하여 여러 번 순환하는 경우, 스택의 크기를 조절하여 메모리 사용량을 줄일 수 있습니다.
스윙(Swing) 서비스와 같은 대규모 어플리케이션에서는 사용자 입력에 따라 데이터의 동적 변경이 발생할 수 있습니다. 이 경우, 각 이벤트에도 최적의 성능을 유지하기 위해 적절한 데이터 구조를 사용하는 것이 중요합니다.
따라서 이 문제는 단순히 오큰수를 구하는 것 이상의 의미를 가질 수 있으며, 메모리 효율성, 처리 성능, 및 활용 가능성 등 다양한 요소들을 고려해야 합니다.