Richest Customer Wealth — 2차원 배열에서 행 합의 최댓값 찾기
전체 보기접기
- 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 1672번 문제는 이렇게 주어집니다.
m x n정수 행렬accounts가 주어집니다.accounts[i][j]는i번째 고객이j번째 은행에 가진 돈입니다. 각 고객의 자산은 그 고객의 모든 은행 계좌 잔액 합입니다. 가장 부유한 고객의 자산을 반환하세요.
예시는 이렇습니다.
accounts = [[1, 2, 3], [3, 2, 1]]→6accounts = [[1, 5], [7, 3], [3, 5]]→10accounts = [[2, 8, 7], [7, 1, 3], [1, 9, 5]]→17
제약은 다음과 같습니다.
m == accounts.lengthn == accounts[i].length1 <= m, n <= 501 <= accounts[i][j] <= 100
핵심은 행렬에서 각 행의 합을 구하고, 그중 최댓값을 찾는 것입니다. 이 글에서는 고객별 자산 배열을 따로 만드는 풀이부터, 최댓값만 유지하는 풀이, Kotlin 표준 함수를 쓰는 간결한 풀이까지 정리하겠습니다. 시간·공간 복잡도 표기가 익숙하지 않다면 시간 복잡도와 공간 복잡도 완전 정복을 먼저 읽어 보시는 것을 권합니다.
문제를 행 기준으로 보기
accounts는 2차원 배열입니다.
accounts = [
[1, 5],
[7, 3],
[3, 5]
]
각 행은 고객 한 명을 의미합니다.
- 1번 고객:
1 + 5 = 6 - 2번 고객:
7 + 3 = 10 - 3번 고객:
3 + 5 = 8
따라서 답은 max(6, 10, 8) = 10입니다.
여기서 열은 은행을 의미하지만, 문제에서 필요한 값은 은행별 합이 아니라 고객별 합입니다. 그래서 바깥 반복은 고객, 안쪽 반복은 그 고객의 은행 계좌를 순회하는 구조가 자연스럽습니다.
Phase 1. 고객별 자산 배열을 먼저 만들기
가장 직관적인 방법은 고객별 자산을 담는 배열을 따로 만든 뒤, 마지막에 최댓값을 찾는 것입니다.
fun maximumWealth(accounts: Array<IntArray>): Int {
val wealths = IntArray(accounts.size)
for (customer in accounts.indices) {
var sum = 0
for (money in accounts[customer]) {
sum += money
}
wealths[customer] = sum
}
var answer = wealths[0]
for (wealth in wealths) {
answer = maxOf(answer, wealth)
}
return answer
}
accounts = [[1, 5], [7, 3], [3, 5]]를 따라가면:
wealths[0] = 1 + 5 = 6
wealths[1] = 7 + 3 = 10
wealths[2] = 3 + 5 = 8
max = 10
이 방식은 문제를 "고객별 자산 계산"과 "최댓값 찾기"로 나눠서 생각할 수 있어 읽기 쉽습니다. 다만 실제로 필요한 것은 최댓값 하나뿐인데, 모든 고객의 자산을 배열에 저장합니다.
시간 복잡도는 O(mn)입니다. 모든 고객의 모든 은행 계좌를 한 번씩 더해야 하기 때문입니다. 공간 복잡도는 wealths 배열 때문에 O(m)입니다.
Phase 2. 최댓값만 유지하며 한 번에 처리하기
자산 배열을 따로 만들 필요는 없습니다. 각 고객의 합을 구하자마자 현재 최댓값과 비교하면 됩니다.
fun maximumWealth(accounts: Array<IntArray>): Int {
var answer = 0
for (customer in accounts) {
var wealth = 0
for (money in customer) {
wealth += money
}
answer = maxOf(answer, wealth)
}
return answer
}
같은 입력을 보면:
customer = [1, 5] -> wealth = 6 -> answer = 6
customer = [7, 3] -> wealth = 10 -> answer = 10
customer = [3, 5] -> wealth = 8 -> answer = 10
answer는 항상 지금까지 확인한 고객 중 가장 큰 자산입니다. 따라서 모든 행을 순회한 뒤에는 전체 고객 중 가장 큰 자산이 남습니다.
이 풀이의 시간 복잡도도 O(mn)입니다. 하지만 추가 배열을 만들지 않으므로 공간 복잡도는 O(1)입니다.
왜 모든 원소를 봐야 하나요?
이 문제는 행별 합의 최댓값을 구해야 합니다. 어떤 고객의 마지막 은행 계좌에 큰 값이 들어 있다면, 그 값 하나 때문에 최댓값이 바뀔 수 있습니다.
예를 들어:
[
[1, 1, 1],
[1, 1, 100]
]
두 번째 행의 마지막 값을 보지 않으면 정답이 102라는 사실을 알 수 없습니다. 따라서 일반적으로는 모든 accounts[i][j]를 확인해야 합니다. 그래서 O(mn)보다 더 낮은 시간 복잡도로 줄이기는 어렵습니다.
Phase 3. Kotlin 표준 함수로 간결하게 쓰기
Kotlin에서는 각 행의 합과 최댓값을 표준 함수로 더 짧게 표현할 수 있습니다.
fun maximumWealth(accounts: Array<IntArray>): Int {
return accounts.maxOf { it.sum() }
}
it.sum()은 고객 한 명의 모든 계좌 잔액을 더합니다. maxOf는 각 고객의 합 중 최댓값을 반환합니다.
이 코드는 Phase 2와 같은 일을 합니다.
accounts.maxOf { customer -> customer.sum() }
다만 알고리즘을 처음 공부할 때는 Phase 2처럼 반복문으로 직접 써 보는 편이 좋습니다. 그래야 "각 행을 더하고, 최댓값을 갱신한다"는 구조가 명확하게 보입니다. 표준 함수 버전은 그 구조를 이해한 뒤에 쓰면 간결합니다.
시간 복잡도는 O(mn), 추가 공간은 O(1)입니다.
answer를 0으로 시작해도 되나요?
이 문제에서는 가능합니다. 제약에서 accounts[i][j] >= 1이 보장되기 때문입니다. 고객은 최소 한 명이고, 은행도 최소 하나이므로 모든 고객의 자산은 최소 1 이상입니다.
var answer = 0
으로 시작해도 첫 고객을 처리하면 반드시 answer가 실제 자산 값으로 갱신됩니다.
만약 계좌 잔액에 음수가 허용되는 문제였다면 0으로 시작하면 안 됩니다. 모든 고객의 합이 음수일 수 있기 때문입니다. 그런 경우에는 첫 고객의 합으로 초기화하거나 Int.MIN_VALUE를 사용해야 합니다.
엣지 케이스
자주 확인해 볼 만한 입력은 이렇습니다.
| 입력 | 결과 | 이유 |
|---|---|---|
[[1]] |
1 |
고객 1명, 은행 1개 |
[[1, 2, 3], [3, 2, 1]] |
6 |
두 고객이 같은 자산 |
[[1, 5], [7, 3], [3, 5]] |
10 |
두 번째 고객이 최대 |
[[2, 8, 7], [7, 1, 3], [1, 9, 5]] |
17 |
첫 번째 고객이 최대 |
[[5], [10], [3]] |
10 |
은행이 하나뿐인 경우 |
[[1, 1, 100], [50, 50, 1]] |
102 |
마지막 계좌 값까지 봐야 함 |
동률은 특별히 처리할 필요가 없습니다. 문제는 가장 부유한 고객의 인덱스가 아니라 자산 값만 요구하므로, 같은 최댓값이 여러 명이어도 그 값만 반환하면 됩니다.
세 풀이를 다시 비교
| 풀이 | 시간 | 공간 | 특징 |
|---|---|---|---|
| 고객별 자산 배열 생성 | O(mn) |
O(m) |
계산 단계가 분리되어 이해하기 쉬움 |
| 최댓값만 유지 | O(mn) |
O(1) |
필요한 값만 저장하는 기본 풀이 |
maxOf { it.sum() } |
O(mn) |
O(1) |
Kotlin답게 가장 간결한 표현 |
마무리
- 이 문제는 2차원 배열에서 행 합의 최댓값을 찾는 문제입니다 — 각 행은 고객, 각 열은 은행 계좌를 의미합니다
- 고객별 자산은 그 행의 모든 값을 더한 값입니다 —
wealth = accounts[i][0] + ... + accounts[i][n - 1] - 최댓값만 필요하므로 모든 고객의 자산 배열을 저장할 필요는 없습니다 — 한 고객을 계산할 때마다
answer만 갱신하면 됩니다 - 시간 복잡도는
O(mn)이 자연스럽습니다 — 어떤 계좌 하나가 최댓값을 바꿀 수 있으므로 모든 원소를 확인해야 합니다 - Kotlin에서는
accounts.maxOf { it.sum() }로 짧게 쓸 수 있습니다 — 다만 반복문 풀이로 구조를 먼저 이해하는 것이 좋습니다
다음으로 읽어볼 글
Find All Numbers Disappeared in an Array — 음수 마킹으로 `O(n)`/`O(1)` 누락 숫자 찾기
1부터 n까지의 숫자 중 배열에 나타나지 않은 값을 찾는 LeetCode 448번, 완전 탐색부터 존재 배열, 음수 제자리 마킹 풀이까지 정리합니다.
Third Maximum Number — 세 변수로 상위 3개를 추적해 찾기
정수 배열에서 세 번째로 큰 서로 다른 값을 찾는 LeetCode 414번, 정렬 풀이부터 세 변수로 상위 3개를 한 번에 추적하는 `O(n)` 풀이까지 정리합니다.
Height Checker — 기대 순서와 다른 위치 세기
학생 키 배열을 정렬했을 때의 기대 순서와 현재 순서를 비교해 다른 인덱스 개수를 세는 LeetCode 1051번, 정렬 비교 풀이부터 카운팅 정렬 풀이까지 정리합니다.