구간 병합
예제 - LeetCode 228. Summary Range
1. 문제 이해
- 주어진 배열에서 연속된 부분을 a->b 또는 a로 압축하여 표현
2. 요구사항 분석
- 입력
- 0 <= nums.length <= 20
- nums[i]는 int 범위
- nums는 오름차순으로 정렬되어 있으며 nums[i]는 중복 없음
- 출력
- 압축한 String 리스트
3. 해결 아이디어
- 연속 구간의 시작점을 설정
- 배열을 순회하며 이전 값과 현재 값을 비교 -> 다르면 연속 구간의 마지막
- 시작점과 마지막을 비교하여 구간이 1개인지 여러개인지 확인 -> 주어진 형식으로 리스트에 추가
- 마지막 구간을 동일하게 처리
4. 시간 복잡도 및 공간 복잡도 확인
- 시간 복잡도
- 배열을 한번만 순회하므로 O(n)
- 공간 복잡도
- 결과를 저장할 리스트가 필요하므로 O(n)
5. 구현
class Solution {
public List<String> summaryRanges(int[] nums) {
// 1. 결과 리스트 할당
List<String> result = new ArrayList<>();
int n = nums.length;
// nums의 길이가 0이면 빈 리스트 반환
if (n == 0) return result;
int start = nums[0]; // 현재 범위 시작점
// 2. 배열을 순회하며 연속된 부분 탐색
for (int i = 1; i < n; i++) {
// 2-1) 연속이 끊기면 범위 기록 후 시작점 갱신
if (nums[i] != nums[i - 1] + 1) {
// 시작점이랑 이전 값이 같음 : 연속된 부분이 1개
if (start == nums[i - 1]) {
result.add(String.valueOf(start));
// 시작점이랑 이전 값이 다름 : 연속된 부분이 여러개, 이전 값이 연속의 마지막
} else {
StringBuilder sb = new StringBuilder();
sb.append(start).append("->").append(nums[i - 1]);
result.add(sb.toString());
}
start = nums[i]; // 새로운 범위 시작
}
}
// 3. 마지막 범위 추가
if (start == nums[n - 1]) {
result.add(String.valueOf(start));
} else {
StringBuilder sb = new StringBuilder();
sb.append(start).append("->").append(nums[n - 1]);
result.add(sb.toString());
}
return result;
}
}
예제 - LeetCode 56. Merge Intervals
1. 문제 이해
- 주어진 구간들 중 겹치는 부분을 병합하여 겹치지 않는 구간들의 배열을 반환
2. 요구사항 분석
- 입력
- 1 <= intervals.length <= 10,000
- intervals[i].length = 2
- 0 <= start <= end <= 10,000
- 출력
- 겹치지 않는 구간들의 배열
3. 해결 아이디어
- 각 구간의 시작 지점을 인덱스로, 최대 도착 지점을 값으로 하는 배열 만들기 -> 정렬을 사용 안하므로 시간 복잡도에 이점이 있음
- 위의 배열을 순회하며 최대 도달 지점 갱신
- 최대 도달 지점과 현재 위치가 같다면 구간의 끝이므로 구간을 추가하고 구간 시작 및 최대 도달 지점 초기화
4. 시간 복잡도 및 공간 복잡도 확인
- 시간 복잡도
- 입력 배열(n)을 2번 최대 도달거리 배열(k)을 1번 순회하므로 O(n + k)
- 공간 복잡도
- 결과 리스트 (n)및 최대 도달 배열(k)을 사용하므로 O(n + k)
5. 구현
class Solution {
public int[][] merge(int[][] intervals) {
// 1. 시작점 중 최대값 계산 -> 시작점을 기준으로 끝점을 기록할 배열을 사용하기 위함
int maxStartPoint = 0;
for (int[] interval : intervals) {
maxStartPoint = Math.max(maxStartPoint, interval[0]);
}
// 2. 시작 위치별로 가장 멀리 갈 수 있는 끝 지점을 기록
int[] maxReach = new int[maxStartPoint + 1];
for (int[] interval : intervals) {
int start = interval[0];
int end = interval[1];
// 도착 지점이 0인 경우 계산의 용이를 위해 기준점을 0이 아닌 1로 수정 -> 이후 계산 시 도착 지점 -1 해주기
maxReach[start] = Math.max(maxReach[start], end + 1);
}
// 3. maxReach를 순회하며 겹치는 구간을 합병
List<int[]> mergedIntervals = new ArrayList<>();
int currentIntervalStart = -1;
int furthestEnd = -1;
for (int position = 0; position < maxReach.length; position++) {
// 3-1) 구간이 존재할 경우
if (maxReach[position] != 0) {
// 구간 시작이 안됐으면 현재 인덱스를 시작점으로 사용
if (currentIntervalStart == -1) {
currentIntervalStart = position;
}
// 가장 멀리 갈 수 있는 구간 갱신
furthestEnd = Math.max(furthestEnd, maxReach[position] - 1);
}
// 현재 위치(인덱스)가 최대 도달 거리이면 구간 추가
if (position == furthestEnd) {
mergedIntervals.add(new int[]{currentIntervalStart, furthestEnd});
currentIntervalStart = -1;
furthestEnd = -1;
}
}
// 4. 아직 구간이 남았다면 마지막 구간 처리
if (currentIntervalStart != -1) {
mergedIntervals.add(new int[]{currentIntervalStart, furthestEnd});
}
// 5. 결과 리스트를 활용하여 2차원 배열로 변환
return mergedIntervals.toArray(new int[mergedIntervals.size()][]);
}
}
예제 - LeetCode 57. Insert Interval
1. 문제 이해
- 기존 오름차순으로 정렬된 구간에 새로운 구간을 삽입하여 정렬된 상태를 유지하면서도 중첩된 구간은 병합하여 반환
2. 요구사항 분석
- 입력
- 1 <= intervals.length <= 10,000
- intervals[i].length = 2
- newInterval.lenght = 2
- 0 <= start <= end <= 10,000
- intervals는 start를 기준으로 오름차순 정렬
- 출력
- 구간을 삽입한 후 배열
3. 해결 아이디어
- 새 구간을 기준으로 이전 구간들을 리스트에 추가
- 새 구간과 겹치는 구간들은 병합 후 리스트에 추가
- 새 구간을 기준으로 이후 구간들을 리스트에 추가
4. 시간 복잡도 및 공간 복잡도 확인
- 시간 복잡도
- intervals를 한 번 순회하므로 O(n)
- 공간 복잡도
- 결과 리스트를 사용하므로 O(n)
5. 구현
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
// 1. 결과를 저장할 배열 할당
List<int[]> result = new ArrayList<>();
int i = 0;
int n = intervals.length;
// 2. newInterval보다 앞에 있는 구간들을 결과에 추가
while (i < n && intervals[i][1] < newInterval[0]) {
result.add(intervals[i]);
i++;
}
// 3. newInterval과 겹치는 구간들을 병합
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.add(newInterval);
// 4. newInterval보다 뒤에 있는 구간들을 결과에 추가
while (i < n) {
result.add(intervals[i]);
i++;
}
return result.toArray(new int[result.size()][]);
}
}
예제 - LeetCode 452. Minimum Number of Arrows to Burst Ballons
1. 문제 이해
- 가로 길이가 있는 여러 풍선들이 주어짐(y좌표는 알 수 없음)
- 특정 x 좌표를 선택해서 화살을 쏠 수 있음
- 모든 풍선을 터뜨릴 수 있는 최소의 화살 개수 구하기
2. 요구사항 분석
- 입력
- 1 <= points.length <= 100,000
- points[i].length = 2
- start <= end
- start와 end는 int 범위
- 출력
- 최소 화살 개수
3. 해결 아이디어
- 풍선의 끝점을 기준으로 정렬
- 풍선들을 순회하며 이전 화살 발사 위치보다 현재 풍선의 시작점이 더 크다면 새로운 화살 필요
- 화살 갯수를 더해주고 발사 위치 갱신
4. 시간 복잡도 및 공간 복잡도 확인
- 시간 복잡도
- 정렬(nlogn)과 순회(n)를 하므로(nlogn > n) O(nlogn)
- 공간 복잡도
- 고정된 크기의 변수만 사용하므로 O(1)
5. 구현
class Solution {
public int findMinArrowShots(int[][] points) {
// 1. 끝점을 기준으로 정렬
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
int arrows = 1;
int lastArrowPos = points[0][1];
for (int i = 1; i < points.length; i++) {
// 2. 현재 풍선이 이전 화살로 터질 수 없으면 새로운 화살 필요
if (points[i][0] > lastArrowPos) {
arrows++;
lastArrowPos = points[i][1];
}
}
return arrows;
}
}
int 범위의 두 변수(start, end)를 사용하므로 long에 두 값을 담고 정렬을 하면
박싱/언박싱 비용, 기본형 비교, 캐시 효율성을 이유로 더 빠른 연산이 가능
class Solution {
public int findMinArrowShots(int[][] points) {
// 1. long 변수의 앞에는 end, 뒤에는 start 지점을 기록
int n = points.length, idx = 0;
long[] sort = new long[n];
for(int[] point : points){
sort[idx++] = (((long) point[1]) << 32) | (point[0] & 0xFFFFFFFFL);
}
// 2. 정렬(end 기준으로 됨) -> 박싱/언박싱, 기본형 비교, 캐시 효율성에 이점
Arrays.sort(sort);
// 3. end와 start를 비교하여 start가 더 크면 화살 갯수를 느리고 end 갱신
int end = (int) (sort[0] >>> 32);
int ans = 1;
for(int i = 1; i < points.length; i++){
// long을 int로 형변환 시 앞의 32자리가 사라짐 -> start
if((int) sort[i] > end){
ans++;
end = (int) (sort[i] >>> 32);
}
}
return ans;
}
}
'코딩테스트 > 알고리즘' 카테고리의 다른 글
예제로 배우는 알고리즘 - 힙 (0) | 2025.05.25 |
---|---|
예제로 배우는 알고리즘 - 스택 (0) | 2025.05.18 |
예제로 배우는 알고리즘 - 행렬 (0) | 2025.05.03 |
예제로 배우는 알고리즘 - 슬라이딩 윈도우 (0) | 2025.05.02 |
예제로 배우는 알고리즘 - 문자열 (0) | 2025.04.28 |