[boj1679] 숫자놀이. DFS, DP, itertools 모두 학습이 가능한 문제

Jonas M·2021년 8월 26일
0

Coding Test

목록 보기
32/33
post-thumbnail

boj1679 숫자놀이 백준 1679
DFS와 DP, itertools로 직접 구현을 모두 해볼 수 있는 문제이면서, 의외로 itertools로 풀었을 때가 DFS보다 더 효율적이었던 문제

Question


nums array와 최대 개수를 주면, nums의 숫자들을 최대 개수 한도에서 아무거나 뽑아서 그 합을 만들 수 있다. 그리고 1부터 숫자를 올려가면서 못 만드는 숫자가 처음 나오면 return.
[1,2]와 2개가 주어진다면 1, 2, 3, 4(2+2)까지 만들 수 있고 답은 5가 되는 것이다. 숫자가 좀 길어지면 중간중간 빈 숫자들이 발생하게 된다.

Solution

우선 공통적으로 memo list를 만들어서 (길이는 max값 * 최대 개수: 만들 수 있는 가장 큰 수) 가능한 숫자들을 True, 불가능하면 False로 할당해준다. 마지막에 1부터 1씩 올라가면서 False를 발견하면 return.

# idx 1이 그대로 숫자 1을 나타낼 수 있도록 앞에 False를 하나 더 붙여줬음
memo = [False for _ in range((nums[-1]*max_num) + 1)]

Solution 1: itertools product

itertools.product는 주어진 iterable 자료 안에서 중복/순서 관계 없이 n번 원소를 추출하는 함수이다.
숫자가 3개 주어지고 최대 5개를 활용할 수 있다면 (0,0,1), (1,2,2), (2,1,1) 등의 각 숫자별 개수 pair를 만들어야 한다. (1,2,2)는 숫자A가 1번, 숫자B가 2번, 숫자C가 2번 포함된 수.
그렇다면 [1,2,3,4,5]에서 중복/순서 관계 없이 3개의 숫자를 뽑으면 되는 것이다. 그리고 (3,4,5)와 같은 경우들은 합이 5가 넘어가므로 불가능하기 때문에 filter로 제거해준다.

pool = list(filter(lambda x: 0<sum(x)<=max_num, list(product(pool, repeat=length))))

pair들을 loop 돌면서 가능한 경우들을 memo에서 제거해주면 된다.
코드는 아래 깃헙에.

Solution 2: DFS

한 step에서 +3, +5로 갈 수 있는 DFS를 돌리면 된다. 주의할 점은 꼭 5개가 되어야만 하는 게 아니기 때문에 매 step 마다 memo를 업데이트해주는 것.

def dfs_helper(cum, length):
	nonlocal nums, max_num, memo
    	memo[cum] = True
        if length == max_num: return

        for num in nums:
            dfs_helper(cum + num, length+1)
    
dfs_helper(0, 0)

시간 초과가 나왔다. itertools는 되는데 이게 시간초과가 나오는 이유는 최대 개수가 커질 때이다. (dfs를 돌아야할 깊이가 깊어질수록) 경우의 수가 급수로 늘어나기 때문이다. (결과는 아래에)

Solution 3: Dynamic Programing

DFS와 유사한 개념이면서 중복 탐색을 줄일 수 있는 방법이다.
dp[i]는 숫자를 i번 골랐을 때 가능한 합들
dp[i+1]은 dp[i]에 각각의 nums를 더해줬을 때 나오는 수들이 되어야 한다.
DFS와 달리 DP에서는 중간 단계들에서 중복되는 수들을 없애줄 수 있다. 그래서 다음 단계로 넘어갈 때 같은 숫자를 탐색하게 되는 경우가 사라지게 된다.
예를 들어, i번째에서 3을 만들 수 있는 경우가 3가지가 있다면, DFS는 그 세 가지 경우에서 모두 i+1번째로 가면서 nums의 숫자들을 더해주게 되지만, DP는 합이 3인 경우엔 하나로 취급하여 셋중 한 가지 경우만 추가로 탐색을 이어간다고 생각할 수 있다.

    for i in range(2, len(dp)):
        before = dp[i-1]
        for b in before:
            for num in nums:
                memo[b+num] = True
                dp[i].add(b+num)

각 i를 set으로 만들어줘서 중복을 피해주고 숫자가 나오는 대로 memo를 업데이트 해준다.

세 가지 솔루션 모든 코드는 github

Result


백준 시스템 안에서 DFS는 시간초과, dp가 itertools에 비해 1/2 정도 시간 단축되지만 itertools를 사용해도 통과가 된다.


짧은 케이스와 중간 케이스 둘을 로컬에서 돌려보았다. 짧을 때는 DFS가 itertools보다 빨랐지만, 길어질수록 효율이 뒤집어진다.

profile
Graduate School of DataScience, NLP researcher

0개의 댓글