이번주 월요일과 화요일에는 개인 사정으로 다른 일을 했다. 오늘부터 문제를 다시 풀어보자.
문제
숫자로만 구성된 문자열 num
이 주어진다. 길이는 n
이다. 모든 인덱스 i
(0 <= i < n
)에 대해, 숫자 i
가 num[i]
횟수만큼 문자열 num
에 나타나는지 확인한다. 이 조건이 모든 인덱스에 대해 참이면 true
를 반환하고, 그렇지 않으면 false
를 반환한다.
예시
예시 1:
입력: num = "1210"
출력: true
설명:
num[0] = '1'. 숫자 0은 num에 1번 나타난다.
num[1] = '2'. 숫자 1은 num에 2번 나타난다.
num[2] = '1'. 숫자 2는 num에 1번 나타난다.
num[3] = '0'. 숫자 3은 num에 0번 나타난다.
예시 2:
입력: num = "030"
출력: false
설명:
num[0] = '0'. 숫자 0은 0번 나타나야 하지만, 실제로는 2번 나타난다.
num[1] = '3'. 숫자 1은 3번 나타나야 하지만, 실제로는 0번 나타난다.
num[2] = '0'. 숫자 2는 num에 0번 나타난다.
첫 번째 접근법: O(n²) 솔루션
처음에는 문자열을 순회하면서 갯수를 세는 간단한 접근법을 생각했다.
class Solution:
def digitCount(self, num: str) -> bool:
for i in range(len(num)):
count = list(num).count(str(i))
if count != int(num[i]):
return False
return True
이 솔루션은 간결하고 이해하기 쉽지만, 시간 복잡도는 O(n²)이다:
- 바깥 반복문은 n번 실행된다 (각 인덱스마다 한 번씩)
- 각 반복마다
count()
메서드가 전체 문자열을 순회한다 (n번의 연산) - 총 시간 복잡도: O(n²)
두 번째 접근법: O(n) 솔루션
해시 맵(Python에서는 딕셔너리)을 사용하여 시간 복잡도를 O(n)으로 줄이는 더 효율적인 접근법을 생각해 보았다.
def digitCount(self, num: str) -> bool:
counts = {}
# 문자열에서 각 숫자의 출현 횟수를 세기 (O(n))
for n in num:
if n in counts:
counts[n] += 1 # (O(1))
else:
counts[n] = 1 # (O(1))
# 각 인덱스 i가 num[i]번만큼 나타나는지 확인 (O(n))
for i in range(len(num)):
if str(i) not in counts: # (O(1))
counts[str(i)] = 0 # (O(1))
if counts[str(i)] != int(num[i]): # (O(1))
return False
return True
최적화된 솔루션의 작동 방식
- 먼저, 문자열에서 각 숫자가 몇 번 나타나는지 추적하는 빈도 맵(
counts
)을 만든다.- 이는 O(n) 시간이 소요된다.
- 그런 다음, 각 인덱스 i를 반복하면서 숫자 i가 빈도 맵에 따라 정확히 num[i]번 나타나는지 확인한다.
- 이 또한 O(n) 시간이 소요된다.
- 총 시간 복잡도는 O(n) + O(n) = O(n)이다.
두 접근법 비교
- 첫 번째 접근법:
- 장점: 공간 복잡도 O(1)
- 단점: 매 반복마다 전체 문자열을 스캔하므로 O(n²) 시간 복잡도를 가진다
- 두 번째 접근법 (해시 맵 사용):
- 장점: 전체 문자열을 한 번만 스캔하므로 O(n) 시간 복잡도를 가진다
- 단점: 공간 복잡도 O(n)
결론
입력 문자열의 길이 제한이 10개로 제한되어 있어 첫 번째 솔루션도 시간 복잡도 측면에서 그렇게 비효율적이라고 생각하지는 않는다.
만약 입력 길이가 매우 커진다면 두 번째 솔루션이 보다 효율적일 것이다.