Python Algorithm Q34
Q34 Permutations
Q
Given an array nums
of distinct integers, return all the possible permutations. You can return the answer in any order.
Sample Input
Example 1
Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
Example 2
Input: nums = [0,1]
Output: [[0,1],[1,0]]
Example 3
Input: nums = [1]
Output: [[1]]
Constraints
1 <= nums.length <= 6
-10 <= nums[i] <= 10
- All the integers of
nums
are unique.
1. My First Try
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
Ans: List[List[int]] = []
# currentlist = 내가 현재 쌓아나가고 있는 list, leftlist = 빠져나가고 있는 list
def recursion(_leftlist: List, _currentlist: List):
# 직접 참조를 막기 위해 복사만 함
leftlist = _leftlist[:]
currentlist = _currentlist[:]
if(len(leftlist) == 0):
# print("ans appended")
Ans.append(currentlist)
return
for N in leftlist:
leftlist.remove(N)
currentlist.append(N)
recursion(leftlist, currentlist)
recursion(nums, [])
return Ans
이 코드를 넣고 돌려보면 얻을 수 있는 결과는 a = [1, 2, 3]
기준으로 [[1, 2, 3], [1, 3, 2]]
이다. 어? 코드를 보면 큰 문제가 없는데 출력은 첫 재귀에서 N = 1 일때만 나오는 것을 볼 수 있다. 왜 문제지 정말 오래 고민했었는데 답은 list는 어딘가에 저장된 value이기 때문에, 내가 그것을 변형하면 다음번 loop에도 영향을 미친다는 점이다. 그렇다. 바로 line 17, 18가 문제이다. 재귀를 할 때 핵심은 recursive한 process를 밟아도 초기 조건이 달라지지 않게 재귀 내부에서만 바뀌도록 해야한다는 것이다. 예를 들어, i += 1 -> dfs(i)
형태가 아니라 dfs(i+1)
형태로 넣어줘야지 기존의 변수에 변화가 생기지 않고 다음 재귀에 변화된 변수를 넣어줄 수 있다. 처음 시도 때는 이걸 몰라가지고 계속 루프가 중간에서 멈췄다…
2. My Own Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
Ans: List[List[int]] = []
def minus(list, int):
templist = list[:]
templist.remove(int)
return templist
# currentlist = 내가 현재 쌓아나가고 있는 list, leftlist = 빠져나가고 있는 list
def recursion(_leftlist: List, _currentlist: List):
# 직접 참조를 막기 위해 복사만 함
leftlist = _leftlist[:]
currentlist = _currentlist[:]
if(len(leftlist) == 0):
# print("ans appended")
Ans.append(currentlist)
return
for N in leftlist:
recursion(minus(leftlist,N), currentlist+[N])
if(nums):
recursion(nums, [])
return Ans
# test set
a = Solution()
b = [1, 2, 3]
a.permute(b)
1번 풀이에서 내가 발견한 문제점을 고쳐서 제출한 풀이다. 결과는 대성공!! line 22를 보면 기존에 위에서 적용했던 append와 remove를 따로 빼서 재귀에 넣는 그 순간에 계산하도록 해서 변수 자체에는 변화가 없도록 했다. recursion의 두번째 인수, currenelist + [N]
를 보면 python lists는 편리하게 덧셈 연산을 지원하기 때문에 append를 쓰지 않아도 편하게 element를 추가할 수 있다. 하지만 - 는 지원하지 않기 때문에, minus라는 function을 위에 구현해서 대신 연산을 했다. 물론 minus 함수 안에서도 list에 직접적으로 변화를 주면 안되기 때문에 철저하게 리스트 그 자체를 참조하지 않게 [:] 형태로 복사해서 가져왔다. 통과하기는 했는데 이렇게 제출한 코드를 남들과 비교해보니까 time이랑 memory 둘다 개많이 잡아먹더라… 조금 아쉬운 결과였던 것 같다. 다만 답 안보고 진짜 혼자 풀어서 좀 뿌듯함 ㅎㅎ
3. Given Solution #1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
results = []
prev_elements = []
def dfs(elements):
if len(elements) == 0:
results.append(prev_elements[:])
for e in elements:
next_elements = elements[:]
next_elements.remove(e)
prev_elements.append(e)
dfs(next_elements)
prev_elements.pop()
dfs(nums)
return results
책에 있던 풀이이다. 여기서 참 인상적이었던 점은 prev_elements를 시점에 맞에 적절하게 이용한다는 점이다. 내가 코드를 짤 때 좀 복잡했던 점은 recursive function의 인수로 left_lest, current_list로 두 가지 list를 받았다. 남아있는 숫자들을 저장하고 있는 left_list와 내가 숫자를 삽입해나가는 current_list를 동시에 인수로 받다보니 두 인수 모두 다 도중에 변형되지 않게 신경을 써야 했다. 근데 여기서 line 14를 보면 정말 기가 막힌다. 여기서 딱 prev를 정의해놓고, 다음 재귀를 시작한다. 그럼 자연스럽게 다음 재귀에서는 이 prev_element를 이용한 재귀를 반복한다! 노트에 정리한 사진을 참고하자.
보면 재귀에 진입하기 전에 앞에까지 쌓아놓고, 재귀 끝나자마자 다시 pop을 이용해서 빼버린다. 정말 깔끔하고 대단한 풀이인 것 같다.
3. Given Solution #2
1
2
3
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
return list(itertools.permutations(nums))
파이썬에 이미 예쁘고 깔끔하게 구현되어있는 라이브러리를 이용한 풀이이다. itertools 모듈인데, 효율적으로 설계된 C라이브러리라 속도도 빠르고 최적화가 잘 되어있다고 한다. 라이브러리 부자다운 파이썬스러운 방식.
참고사항
https://crackerjacks.tistory.com/14 위에서 언급했던 list의 copy에 대해 자세히 다룬 자료이다. list, set, dictionary는 mutable한 variable이기 때문에 리스트 슬라이싱, .copy() 등의 방식이 아니라 직접 할당을 할 경우 객체 자체에 대한 참조가 되어 A에서는 바꾸지 않았지만 B에서 바꿨으면 A도 동일하게 바뀌는 경우가 발생한다. 보통 리스트 복사에서 이를 방지하는 가장 좋은 방법은 리스트 슬라이싱
a = b[:]
이다. 많이 애용하자.
Overall Review
스스로 풀었다는 점에서 일단 뿌듯하다! 다만 주어진 풀이에서 보여준 방식이 굉장히 깔끔하기 때문에 다음에 풀이를 짤 때는 재귀에 들어가는 시점을 잘 파악하고 설계하면 보다 깔끔한 답안을 낼 수 있을 것 같다.