문제 설명
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(n2)의 시간복잡도를 갖기 때문이라고 생각했는데 빙고 처리를 아래와 같이 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(n2)의 시간복잡도가 발생했을 뿐 아니라 board의 특정 자리 값이 nums에 있는지를 확인하는 과정에서 O(n)의 시간복잡도가 추가적으로 들고, 결국엔 O(n3)이라는 매우 큰 시간 복잡도를 갖게 되었다.
하지만, 이런 탐색 과정은 매우 필수적이기 때문에 탐색을 하지 않을 수는 없었다. 이에 좀 더 알아보니 nums는 리스트이기 때문에 탐색 시 O(n)의 시간 복잡도가 소요되지만, set 자료형을 사용하여 type-casting을 해주면 O(1)로 탐색이 가능하다. 이는 set이 해시테이블로 구현이 되었기 때문이다.
해시테이블 같은 경우에는 일반적으로 O(1)의 시간복잡도로 탐색이 가능하다. 해시에 대해서는 나중에 보다 자세히 정리해보도록 하겠다.
결국 set으로 nums를 타입 변경해줌으로써 결과적으로 bottleneck은 O(n2)이 되었고, 이로써 시간 초과 문제는 해결하였다.
로직 개선에 대한 추가 설명
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 |