DFS [리트코드] 46. Permutations, 77. Combinations

이영준·2022년 10월 17일
0

알고리즘 문제풀이

목록 보기
13/24

문제링크

순열

깊이 우선 탐색의 대표격인 순열 문제이다.
DFS 함수를 돌려 깊이가 순열의 크기에 도달할 때까지 다시 DFS를 반복적으로 시행하도록 처리해준다.

from typing import List


class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []

        def dfs(length: int, dfs_nums: List):
            if length == len(nums):
                res.append(dfs_nums)
                return
            else:
                for val in list(x for x in nums if x not in dfs_nums):
                    print(f'dfs({length+1},{dfs_nums+[val]})')
                    dfs(length+1, dfs_nums+[val])

        dfs(0, [])
        return res


print(Solution.permute(Solution(),
                       [1, 2, 3]))

dfs(1,[1])
dfs(2,[1, 2])
dfs(3,[1, 2, 3])
dfs(2,[1, 3])
dfs(3,[1, 3, 2])
dfs(1,[2])
dfs(2,[2, 1])
dfs(3,[2, 1, 3])
dfs(2,[2, 3])
dfs(3,[2, 3, 1])
dfs(1,[3])
dfs(2,[3, 1])
dfs(3,[3, 1, 2])
dfs(2,[3, 2])
dfs(3,[3, 2, 1])

[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

여기서 중요한 것은 dsf함수의 인자로 사용되는 dfs_nums를 직접적으로 바꾸면 안된다는 것이다.

예를 들어 초기답은 이랬는데,

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []

        def dfs(length: int, dfs_nums: List):
            if length == len(nums):
                res.append(dfs_nums)
                return
            else:
                for i in range(length, len(nums)):
                    dfs_nums.append(nums[i])
                    print(f'dfs({length+1},{dfs_nums})')
                    dfs(length+1, dfs_nums)
        dfs(0, [])
        return res


print(Solution.permute(Solution(),
                       [1, 2, 3]))

dfs(1,[1])
dfs(2,[1, 2])
dfs(3,[1, 2, 3])
dfs(2,[1, 2, 3, 3])
dfs(3,[1, 2, 3, 3, 3])
dfs(1,[1, 2, 3, 3, 3, 2])
dfs(2,[1, 2, 3, 3, 3, 2, 2])
dfs(3,[1, 2, 3, 3, 3, 2, 2, 3])
dfs(2,[1, 2, 3, 3, 3, 2, 2, 3, 3])
dfs(3,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3])
dfs(1,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3])
dfs(2,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2])
dfs(3,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3])
dfs(2,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3])
dfs(3,[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3])

[[1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3], [1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3], [1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3], [1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3], [1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3], [1, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3]]

재귀적으로 dfs 함수가 돌때마다 dfs_nums 변수를 바꾸다보니 상황에 따라 초기화가 되지 않은 리스트로 커지게 되는 문제가 있었다. 따라서 재귀적으로 함수를 구성할 때는 변수 재할당을 매우 유의해야 한다.

조합

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []

        def dfs(length, dfs_nums):

            if length == k:
                res.append(dfs_nums)
                return
            else:
                for val in list(x for x in range(dfs_nums[-1] if dfs_nums else 1, n+1) if x not in dfs_nums):
                    # print(f'dfs({length+1},{dfs_nums+[val]})')
                    dfs(length+1, dfs_nums+[val])
        dfs(0, [])
        return res


print(Solution.combine(Solution(), 5, 3))

순열에서 이미 사용된 숫자는 같은 계층의 다음 탐색에서 버리기 위하여
for val in list(x for x in range(dfs_nums[-1] if dfs_nums else 1, n+1) if x not in dfs_nums):
와 같이 range를 dfs의 length가 바뀔 때마다 늘려주었다.

from typing import List


class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        results = []

        def dfs(elements, start: int, k: int):
            if k == 0:
                results.append(elements[:])
                return

            # 자신 이전의 모든 값을 고정하여 재귀 호출
            for i in range(start, n + 1):
                elements.append(i)
                print(f'dfs({elements}, {i+1}, {k-1})')
                dfs(elements, i + 1, k - 1)
                elements.pop()

        dfs([], 1, k)
        return results


print(Solution.combine(Solution(), 5, 3))

모범답안에 대한 이해는 추후 하는 걸로,,, 여기서는 start를 통하여 이미 쓰인 숫자를 제외한 숫자만을 재귀적으로 호출하느 ㄴ것으로 보인다.

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글

관련 채용 정보