최근 파이썬으로 순열을 짜다가 놓치고 있는 게 있는 것 같아 글을 쓴다. 

 


 

다음과 같이 1번 코드, 2번 코드는 순열(permutation)을 재귀 함수로 푸는 코드이다. 차이점은 5번째 줄 arr.append()에 파라미터로 tmp를 주느냐, tmp[:]를 주느냐의 차이다. 

    ## 1번 코드
    def permute(self, nums: List[int]) -> List[List[int]]:
        arr = []
        def backtrack(tmp = []):
            if len(tmp) == len(nums):
                arr.append(tmp)
                return
            for n in nums:
                if n in tmp:
                    pass
                else:
                    tmp.append(n)
                    backtrack(tmp)
                    tmp.pop()
            return
        backtrack()
        return arr
    ## 2번 코드
    def permute(self, nums: List[int]) -> List[List[int]]:
        arr = []
        def backtrack(tmp = []):
            if len(tmp) == len(nums):
                arr.append(tmp[:]) #1번 코드와 다른점
                return
            for n in nums:
                if n in tmp:
                    pass
                else:
                    tmp.append(n)
                    backtrack(tmp)
                    tmp.pop()
            return
        backtrack()
        return arr

 

 

 

 

하지만 각 코드의 결과는 다르다.

#1번코드 결과
permute([1,2,3]) # [[],[],[],[],[],[]]

#2번코드 결과
permute([1,2,3]) # [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

 

 

 

 

 

그이유는 arr 안에 들어가는 tmp는 유동적이지만 tmp의 주소는 바뀌지 않기 때문이다. tmp[:]은 주소는 제외하고 copy 할 수 있다. 따라서 1번 코드와 2번 코드의 6번째 줄에 print(arr)을 추가해보면 재밌는 결과가 나온다.

#1번코드
def combine(self, n: int, k: int) -> List[List[int]]:
    arr = []
        
    def backtrack(tmp = [], start=1):
        if len(tmp) == k:
            arr.append(tmp)
            print(arr)
            return
        for i in range(start, n+1)
        	tmp.append(i)
        	backtrack(tmp, i+1)
            tmp.pop()
        return
    backtrack()
    return arr
    
combine([1,2,3])
#[[1, 2]]
#[[1, 3], [1, 3]]
#[[1, 4], [1, 4], [1, 4]]
#[[2, 3], [2, 3], [2, 3], [2, 3]]
#[[2, 4], [2, 4], [2, 4], [2, 4], [2, 4]]
#[[3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4]]
#2번코드
def combine(self, n: int, k: int) -> List[List[int]]:
    arr = []
        
    def backtrack(tmp = [], start=1):
        if len(tmp) == k:
            arr.append(tmp[:])
            print(arr)
            return
        for i in range(start, n+1)
        	tmp.append(i)
        	backtrack(tmp, i+1)
            tmp.pop()
        return
    backtrack()
    return arr
    
combine([1,2,3])
#[[1, 2]]
#[[1, 2], [1, 3]]
#[[1, 2], [1, 3], [1, 4]]
#[[1, 2], [1, 3], [1, 4], [2, 3]]
#[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4]]
#[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

위와 같이 1번 코드는 tmp의 값이 재귀를 돌면서 계속 변하는데, 변한 마지막 값으로 같은 주소를 공유하는 tmp들이 다 변한다. 

 

 

 

 

 


 

파이썬으로 코딩하다보면 가끔 포인터, 주소에 대해 무신경해질 때가 가끔 있는 듯하다. 놓치지 말고 잘 챙기자.

728x90

+ Recent posts