문제 설명
N*N 숫자 입력과 지운 숫자가 들어가있는 nums 배열이 있다고 했을 때 총 몇 개의 빙고가 발생(?)하는지를 리턴하는 문제
나의 풀이
시간 초과 발생.
def solution(board, nums):
answer = 0
n = len(board)
for row in range(n):
for col in range(n):
if board[row][col] in nums:
board[row][col] = 0
# 오른쪽으로 이동 (horizontal 빙고)
for i in range(n):
for j in range(n-1):
if board[i][j] != board[i][j+1]:
break
else:
answer += 1
# 아래로 이동 (vertical 빙고)
for i in range(n):
for j in range(n-1):
if board[j][i] != board[j+1][i]:
break
else:
answer += 1
# diagonal
for i in range(n-1):
if board[i][i] != board[i+1][i+1]:
break
else:
answer += 1
# off-diagonal
for i in range(n-1):
if board[i][n-1-i] != board[i+1][n-1-(i+1)]:
break
else:
answer += 1
return answer
로직 상으로는 문제가 없지만, 시간 초과가 떠서 효율성에서 0점 처리 됐다. 그 이유를 처음에는 horizontal, vertical 빙고를 찾으면서 $O(n^2)$의 시간복잡도를 갖기 때문이라고 생각했는데 빙고 처리를 아래와 같이 summation하는 방식으로 변경했는데도 시간 초과가 발생했다.
def solution(...):
...
# 각 빙고 방향에 대해서 다 룰 베이스로 판단하기.
answer += len([i for i in board if sum(i) == 0]) # horizontal
answer += len([i for i in zip(*board) if sum(i) == 0]) # vertical
answer += int(sum(board[i][i] for i in range(n)) == 0) # diagonal
answer += int(sum(board[n - 1 - i][i] for i in range(n)) == 0) # off-diagonal
return answer
곰곰히 생각해보니, 문제는 빙고를 찾는 부분이 아니라 nums의 자리를 처리하는 부분이었다. 이미 for loop을 두 번 돌면서 $O(n^2)$의 시간복잡도가 발생했을 뿐 아니라 board의 특정 자리 값이 nums에 있는지를 확인하는 과정에서 $O(n)$의 시간복잡도가 추가적으로 들고, 결국엔 $O(n^3)$이라는 매우 큰 시간 복잡도를 갖게 되었다.
하지만, 이런 탐색 과정은 매우 필수적이기 때문에 탐색을 하지 않을 수는 없었다. 이에 좀 더 알아보니 nums는 리스트이기 때문에 탐색 시 $O(n)$의 시간 복잡도가 소요되지만, set 자료형을 사용하여 type-casting을 해주면 $O(1)$로 탐색이 가능하다. 이는 set이 해시테이블로 구현이 되었기 때문이다.
해시테이블 같은 경우에는 일반적으로 O(1)의 시간복잡도로 탐색이 가능하다. 해시에 대해서는 나중에 보다 자세히 정리해보도록 하겠다.
결국 set으로 nums를 타입 변경해줌으로써 결과적으로 bottleneck은 $O(n^2)$이 되었고, 이로써 시간 초과 문제는 해결하였다.
로직 개선에 대한 추가 설명
def solution(...):
...
# 각 빙고 방향에 대해서 다 룰 베이스로 판단하기.
answer += len([i for i in board if sum(i) == 0]) # horizontal
answer += len([i for i in zip(*board) if sum(i) == 0]) # vertical
answer += int(sum(board[i][i] for i in range(n)) == 0) # diagonal
answer += int(sum(board[n - 1 - i][i] for i in range(n)) == 0) # off-diagonal
return answer
vertical 빙고를 판단할 때에 zip함수에 board를 풀어줘서 넣어주는 방식이 매우 신기했다. 놀랍게도 저런 식으로 zip함수를 사용하면 matrix를 같은 열 & 다른 행에 있는 요소끼리 묶어줄 수 있었다.
임의로 (5,5)의 행렬을 만들어서 테스트해본 결과이다. print(*mat)을 수행했을 때, 각 행을 나열하였다. 따라서 이를 zip해주는 경우에는 각 행의 요소를 묶어서 리턴할 것이다. 따라서 다음과 같이 같은 열 & 다른 행의 요소를 묶어서 나열할 수 있었던 것이다. 위와 같이 행 간 summation을 이용해 vertical 빙고를 찾아내는 룰을 세울 수 있었다.
최종 코드
def solution(board, nums):
answer = 0
n = len(board)
nums = set(nums)
for row in range(n):
for col in range(n):
if board[row][col] in nums:
board[row][col] = 0
# horizontal (각 행을 탐색하면서 sum = 0인지 체크)
answer += len([i for i in board if sum(i)==0])
# vertical (각 열을 탐색하면서 sum = 0인지 체크)
answer += len([i for i in zip(*board) if sum(i)==0])
# diagonal (대각요소의 합이 0인지 체크)
answer += int(sum(board[i][i] for i in range(n))==0)
# off-diagonal
answer += int(sum(board[n-i-1][i] for i in range(n))==0)
return answer
정리
- set은 해시테이블로 구현되어 있기 때문에 시간 복잡도가 $O(1)$이다.
- zip(*2D-array)를 수행하면, 같은 열 내 다른 행의 요소끼리 묶어줄 수 있다. (열 별로 slicing 가능)
'프로그래밍 > 알고리즘 문제풀이' 카테고리의 다른 글
[Python] 프로그래머스 - 2xn 타일링 (2) | 2022.05.27 |
---|---|
[Python] 프로그래머스 - 네트워크 (0) | 2022.05.15 |
[python] 프로그래머스 - 전염병 (0) | 2022.01.15 |
[python] 프로그래머스 - 문자열 압축 (0) | 2022.01.13 |
[python] 프로그래머스 - 게임아이템 (0) | 2022.01.11 |