mirror of
https://github.com/krahets/hello-algo.git
synced 2026-11-01 18:55:23 +08:00
* feat(ko): add the complete Korean online edition * Use UTF-8 for Python CI output on Windows * Remove print-book promotion from the Korean site * fix(ko): preserve emphasis before Korean particles
5.0 KiB
5.0 KiB
연습 문제
개념 확인
어떤 작업에 분할 정복이 적합한가
한 학생이 ‘먼저 둘로 나누고, 각각 해결한 뒤, 결과를 합치는’ 방식으로 다음 작업을 수행하려 한다. 각 작업이 ‘분할 정복에 적합함’, ‘분할 정복이 가능하지만 전체 작업량은 줄어들지 않음’, ‘두 부분을 독립적으로 해결할 수 없음’ 중 어디에 해당하는지 판단하고 이유를 설명하라.
- 정렬되지 않은 배열을 정렬한다.
- 배열의 최댓값을 구한다.
- 일련의 스택 연산
push(x),pop()을 순서대로 실행하고 각pop()에서 얻은 원소를 출력한다.
??? success "참고 답안"
1. 적합함: 반으로 나눈 뒤 두 부분을 독립적으로 정렬하고, $O(n)$ 시간에 병합한다. 이것이 바로 병합 정렬이다.
2. 분할 정복이 가능하지만 전체 작업량은 줄어들지 않음: 왼쪽과 오른쪽을 합치면 여전히 $n$개 원소를 모두 확인해야 하므로,
직접 순회하는 것과 마찬가지로 $O(n)$ 시간이 든다.
3. 두 부분을 독립적으로 해결할 수 없음: 후반부를 시작할 때의 스택 내용은 전반부의 실행 결과에 따라 달라진다.
따라서 서로의 결과를 모르는 상태에서 두 부분을 독립적으로 완료할 수 없다.
빠른 거듭제곱은 계산을 어떻게 줄이는가
다음 재귀 함수는 분할 정복으로 $x^n$을 계산한다.
[file]{fast_power}-[class]{}-[func]{fast_pow}
x = 3, n = 5로 설정하고 이 함수를 실행한다.
- 재귀 호출에서 매개변수
n은 차례대로 어떤 값이 되는가? - 가장 깊은 호출부터 반환할 때 각 호출은 차례대로 어떤 값을 반환하는가?
- 곱셈 양쪽에서 같은 부분 문제를 한 번씩 호출하지 않고, 먼저 재귀 호출의 결과를
half에 저장하는 이유는 무엇인가?
??? success "참고 답안"
1. 매개변수는 `5 → 2 → 1 → 0` 순으로 바뀐다. 종료 조건에 도달할 때까지 매번 지수를 절반으로 줄인다.
2. `n = 0`이면 1을 반환한다. `n = 1`이면 $1×1×3=3$을 반환한다.
`n = 2`이면 $3×3=9$를, `n = 5`이면 $9×9×3=243$을 반환한다.
3. 곱셈 양쪽에서 같은 부분 문제를 한 번씩 호출하면 두 재귀 호출은 완전히 같은 계산을 수행한다.
먼저 결과를 `half`에 저장하면 각 단계에서 재귀 호출을 한 번만 하면 되고, 재귀 깊이는 대략 $\log n$이다.
두 번 호출하면 같은 계산이 대량으로 반복된다.
순회 결과에서 왼쪽 서브트리와 오른쪽 서브트리 나누기
중복된 노드가 없는 이진 트리의 전위 순회와 중위 순회 결과는 다음과 같다.
- 전위 순회:
[A, B, D, E, C] - 중위 순회:
[D, B, E, A, C]
루트 노드 수준에서만 나누고, 더 깊이 재귀적으로 나누거나 트리 전체를 그릴 필요는 없다.
- 루트 노드는 무엇인가?
- 중위 순회에서 왼쪽 서브트리와 오른쪽 서브트리에 해당하는 구간은 각각 어디인가?
- 전위 순회에서 왼쪽 서브트리와 오른쪽 서브트리에 해당하는 구간은 각각 어디인가? 루트 노드의 직접적인 자식은 누구인가?
??? success "참고 답안"
1. 전위 순회의 첫 번째 노드가 루트 노드이므로 루트는 `A`다.
2. `A`를 기준으로 중위 순회가 두 부분으로 나뉜다. 왼쪽 서브트리는 `[D, B, E]`, 오른쪽 서브트리는 `[C]`다.
3. 왼쪽 서브트리에 노드가 3개 있으므로 전위 순회에서 루트 노드 `A` 다음의 원소 3개가 왼쪽 서브트리에 속한다.
즉 `[B, D, E]`가 왼쪽 서브트리이고, 나머지 `[C]`가 오른쪽 서브트리다.
따라서 루트 노드의 왼쪽 자식은 `B`, 오른쪽 자식은 `C`다.
프로그래밍 연습
빠른 거듭제곱
실수 x와 정수 n이 주어진다. 언어에서 제공하는 거듭제곱 함수를 호출하지 않고 $x^n$을 계산하라.
재귀적인 분할 정복을 사용하여 매번 지수를 절반으로 줄이고, 이미 계산한 부분 문제의 결과를 재사용해야 한다.
이 문제에서는 x = 0인 경우를 포함하여 $x^0=1$로 정한다. n < 0일 때는 x != 0임이 보장되며, 답을 $(1/x)^{-n}$으로 바꾸어 구할 수 있다.
??? tip "풀이 힌트"
1. n이 0이면 답은 1이다.
2. x의 n // 2제곱을 계산하여 half에 저장한다. 같은 재귀 호출을 두 번 하지 마라.
3. n < 0이면 먼저 x를 1 / x로 바꾸고 n을 -n으로 바꾼다. C++이나 Java에서는 n을 먼저 64비트 정수로 변환하면 가장 작은 32비트 정수의 부호를 반대로 바꿀 때 발생하는 오버플로를 피할 수 있다.
LeetCode{ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }