Product of Array Except Self — 나눗셈 없이 `O(n)`/`O(1)`로 나머지 곱 구하기
전체 보기접기
- 01Merge Strings Alternately — 단일 루프로 `O(n + m)` 교차 병합
- 02Increasing Triplet Subsequence — `O(n)` 시간, `O(1)` 공간으로 푸는 그리디 패턴
- 03Move Zeroes — 쓰기 포인터 하나로 `O(n)`/`O(1)` 제자리 재배치
- 04String Compression — 읽기/쓰기 포인터로 `O(n)`/`O(1)` 제자리 압축
- 05Product of Array Except Self — 나눗셈 없이 `O(n)`/`O(1)`로 나머지 곱 구하기읽는 중
- 06Reverse Vowels of a String — 투 포인터 `O(n)` 스왑으로 모음만 뒤집기
- 07Can Place Flowers — `O(n)` 그리디 스캔으로 인접 금지 배치 판정
- 08시간 복잡도와 공간 복잡도 완전 정복 — Big-O 표기법부터 실전 분석까지
- 09Greatest Common Divisor of Strings — `GCD`로 `O(n + m)` 문자열 최대 공약 패턴
- 10Container With Most Water — 투 포인터 `O(n)`로 최대 넓이 한 번에 찾기
- 11Is Subsequence — 투 포인터 `O(|s| + |t|)`로 부분 수열 판정
- 12Richest Customer Wealth — 2차원 배열에서 행 합의 최댓값 찾기
- 13Running Sum of 1d Array — 누적합의 가장 기본 형태
- 14Roman to Integer — 감산 규칙을 한 번의 스캔으로 처리하기
- 15Palindrome Number — 문자열 없이 절반만 뒤집는 숫자 팰린드롬 판정
- 16Max Number of K-Sum Pairs — 정렬 `O(n log n)`에서 해시 `O(n)`까지 최대 페어 수 세기
- 17Ransom Note — 문자 개수로 문자열 구성 가능 여부 판정하기
- 18Middle of the Linked List — slow/fast 포인터로 중간 노드 찾기
- 19Max Consecutive Ones — 한 번의 스캔으로 가장 긴 1 구간 찾기
- 20Find Numbers with Even Number of Digits — 자릿수 개수의 짝수 여부 세기
- 21Merge Sorted Array — 뒤에서부터 채우는 제자리 병합
- 22Duplicate Zeros — 고정 길이 배열을 오른쪽에서부터 제자리 갱신하기
- 23Squares of a Sorted Array — 절댓값 기준 투 포인터로 `O(n)` 정렬 유지하기
- 24Valid Mountain Array — 올라갔다가 내려오는 배열 판별하기
- 25Check If N and Its Double Exist — 어떤 값과 그 두 배가 함께 있는지 확인하기
- 26Remove Duplicates from Sorted Array — 정렬 배열을 고유 값 구간으로 압축하기
- 27Remove Element — 앞쪽 유효 구간으로 압축하는 제자리 제거
- 28Replace Elements with Greatest Element on Right Side — 오른쪽 최댓값으로 바꾸기
- 29Sort Array By Parity — 짝수를 앞쪽으로 모으기
- 30Height Checker — 기대 순서와 다른 위치 세기
- 31Third Maximum Number — 세 변수로 상위 3개를 추적해 찾기
- 32Reorder Data in Log Files — 문자 로그만 정렬하고 숫자 로그는 순서 유지하기
- 33Valid Palindrome — 투 포인터로 영숫자만 비교하는 팰린드롬 판정
- 34Find All Numbers Disappeared in an Array — 음수 마킹으로 `O(n)`/`O(1)` 누락 숫자 찾기
- 35Most Common Word — 금지어를 제외하고 가장 자주 나온 단어 찾기
- 36Group Anagrams — 정렬 키와 문자 개수 키로 애너그램 묶기
나를 제외한 곱, 어떤 문제인가요?
LeetCode 238번 문제는 이렇게 주어집니다.
정수 배열
nums가 주어졌을 때,answer[i]가nums[i]를 제외한 나머지 모든 원소의 곱이 되는 배열answer를 반환하세요. 나눗셈을 사용하지 않고O(n)에 풀어야 합니다.
예시는 이렇습니다.
nums = [1, 2, 3, 4]→[24, 12, 8, 6]nums = [-1, 1, 0, -3, 3]→[0, 0, 9, 0, 0]
제약은 다음과 같습니다.
2 <= nums.length <= 10^5-30 <= nums[i] <= 30- 곱은 32비트 정수 범위 안에 들어옴
나눗셈(전체 곱 / nums[i])을 쓰면 간단하지만, 문제가 명시적으로 금지합니다. 0이 포함된 경우 나눗셈 자체가 불가능하기도 합니다. 이 글에서는 이중 반복 브루트포스부터, 접두사·접미사 곱 배열, 그리고 변수 하나로 공간을 줄이는 풀이까지 단계적으로 정리합니다. 시간·공간 복잡도 표기가 익숙하지 않다면 시간 복잡도와 공간 복잡도 완전 정복을 먼저 읽어 보시는 것을 권합니다.
Phase 1. 브루트포스 — 매번 나머지를 곱하기
가장 직관적인 접근은 각 인덱스마다 자기를 건너뛰고 나머지를 모두 곱하는 것입니다.
fun productExceptSelf(nums: IntArray): IntArray {
val n = nums.size
val answer = IntArray(n)
for (i in 0 until n) {
var product = 1
for (j in 0 until n) {
if (j != i) product *= nums[j]
}
answer[i] = product
}
return answer
}
nums = [1, 2, 3, 4]를 따라가 봅시다.
i = 0:2 × 3 × 4 = 24i = 1:1 × 3 × 4 = 12i = 2:1 × 2 × 4 = 8i = 3:1 × 2 × 3 = 6
정답은 맞지만, 시간 복잡도가 O(n²)입니다. n이 최대 10^5이면 10^10번 연산이며, 제한 시간 안에 끝나지 않습니다.
Phase 2. 접두사 곱과 접미사 곱 — O(n) 시간
핵심 관찰: answer[i] = i 왼쪽 원소들의 곱 × i 오른쪽 원소들의 곱입니다.
nums = [1, 2, 3, 4]
prefix = [1, 1, 2, 6] ← i 왼쪽까지의 곱
suffix = [24, 12, 4, 1] ← i 오른쪽까지의 곱
answer = [1×24, 1×12, 2×4, 6×1] = [24, 12, 8, 6]
이 두 배열을 미리 만들면 됩니다.
fun productExceptSelf(nums: IntArray): IntArray {
val n = nums.size
val prefix = IntArray(n)
val suffix = IntArray(n)
prefix[0] = 1
for (i in 1 until n) {
prefix[i] = prefix[i - 1] * nums[i - 1]
}
suffix[n - 1] = 1
for (i in n - 2 downTo 0) {
suffix[i] = suffix[i + 1] * nums[i + 1]
}
val answer = IntArray(n)
for (i in 0 until n) {
answer[i] = prefix[i] * suffix[i]
}
return answer
}
시간 O(n), 공간 O(n) (prefix + suffix 배열)입니다. 나눗셈 없이 O(n)을 달성했지만, 보조 배열 두 개가 필요합니다.
Phase 3. 출력 배열 하나로 줄이기 — O(1) 추가 공간
관찰: prefix 배열은 answer에 직접 쓸 수 있고, suffix는 오른쪽에서 훑으면서 변수 하나로 누적하면 됩니다.
fun productExceptSelf(nums: IntArray): IntArray {
val n = nums.size
val answer = IntArray(n)
answer[0] = 1
for (i in 1 until n) {
answer[i] = answer[i - 1] * nums[i - 1]
}
var suffix = 1
for (i in n - 1 downTo 0) {
answer[i] *= suffix
suffix *= nums[i]
}
return answer
}
nums = [1, 2, 3, 4]를 따라가 봅시다.
1단계 — 접두사 곱을 answer에 기록:
answer[0] = 1answer[1] = 1 × 1 = 1answer[2] = 1 × 2 = 2answer[3] = 2 × 3 = 6answer = [1, 1, 2, 6]
2단계 — 오른쪽에서 접미사 곱을 누적하며 곱하기:
i = 3:answer[3] = 6 × 1 = 6,suffix = 1 × 4 = 4i = 2:answer[2] = 2 × 4 = 8,suffix = 4 × 3 = 12i = 1:answer[1] = 1 × 12 = 12,suffix = 12 × 2 = 24i = 0:answer[0] = 1 × 24 = 24,suffix = 24 × 1 = 24answer = [24, 12, 8, 6]✓
시간 O(n), 추가 공간 O(1)입니다. 출력 배열 answer는 문제가 요구하는 결과이므로 보조 공간에 포함하지 않습니다.
0이 포함된 경우도 맞나요?
nums = [-1, 1, 0, -3, 3]을 따라가 봅시다.
1단계 — 접두사 곱:
answer = [1, -1, -1, 0, 0]
2단계 — 접미사 곱 누적:
i = 4:answer[4] = 0 × 1 = 0,suffix = 3i = 3:answer[3] = 0 × 3 = 0,suffix = -9i = 2:answer[2] = -1 × -9 = 9,suffix = 0i = 1:answer[1] = -1 × 0 = 0,suffix = 0i = 0:answer[0] = 1 × 0 = 0,suffix = 0answer = [0, 0, 9, 0, 0]✓
0이 있는 위치의 접미사(또는 접두사)가 0이 되면서 자연스럽게 처리됩니다. 0을 제외한 위치(i = 2)만 양쪽 곱이 0이 아닌 값으로 남습니다.
세 풀이를 다시 비교
| 풀이 | 시간 | 공간 | 특징 |
|---|---|---|---|
| 브루트포스 (이중 반복) | O(n²) |
O(1) |
직관적이지만 큰 입력에서 시간 초과 |
| 접두사 + 접미사 배열 | O(n) |
O(n) |
두 방향 스캔, 관찰을 그대로 옮긴 중간 단계 |
| 출력 배열 + 접미사 변수 | O(n) |
O(1) |
접두사를 출력에 직접 기록, 접미사는 변수 하나로 누적 |
마무리
- "자기를 제외한 곱 = 왼쪽 곱 × 오른쪽 곱"이라는 분할이 이 문제의 핵심 — 나눗셈 없이 곱을 구할 수 있는 이유가 여기에 있습니다
- 접두사·접미사 배열을 먼저 떠올리면, 출력 배열 재활용 + 변수 하나로 공간을 줄이는 단계가 자연스럽게 따라온다 — Phase 2에서 Phase 3으로의 전환은 "같은 관찰을 더 적은 공간으로 표현하는 것"입니다
0이 포함된 입력은 별도 분기 없이 자연스럽게 처리된다 — 접두사나 접미사에0이 곱해지면 해당 방향의 누적 곱이0으로 전파되기 때문입니다- Phase 2 → Phase 3의 공간 압축은 Increasing Triplet Subsequence의
leftMin/rightMax배열 → 변수 두 개 압축과 같은 패턴 — "양방향 전처리를 한 방향은 배열에, 다른 방향은 변수에" 담는 기법은 여러 문제에서 반복됩니다
String Compression — 읽기/쓰기 포인터로 `O(n)`/`O(1)` 제자리 압축
다음 편Reverse Vowels of a String — 투 포인터 `O(n)` 스왑으로 모음만 뒤집기
다음으로 읽어볼 글
Group Anagrams — 정렬 키와 문자 개수 키로 애너그램 묶기
애너그램 문자열을 같은 그룹으로 묶는 LeetCode 49번, 정렬된 문자열을 키로 쓰는 풀이부터 문자 개수 배열을 해시 키로 바꾸는 `O(n * k)` 풀이까지 정리합니다.
Most Common Word — 금지어를 제외하고 가장 자주 나온 단어 찾기
문단에서 금지어를 제외하고 가장 많이 등장한 단어를 찾는 LeetCode 819번, 문장 부호 제거와 소문자 정규화, 해시 카운팅 풀이를 정리합니다.
Find All Numbers Disappeared in an Array — 음수 마킹으로 `O(n)`/`O(1)` 누락 숫자 찾기
1부터 n까지의 숫자 중 배열에 나타나지 않은 값을 찾는 LeetCode 448번, 완전 탐색부터 존재 배열, 음수 제자리 마킹 풀이까지 정리합니다.