Python Algorithm Q35

Q35 Combinations


Q

Given two integers n and k, return all possible combinations of k numbers chosen from the range [1, n].

You may return the answer in any order.

Sample Input

Example 1

Input: n = 4, k = 2
Output: [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
Explanation: There are 4 choose 2 = 6 total combinations.
Note that combinations are unordered, i.e., [1,2] and [2,1] are considered to be the same combination.

Example 2

Input: n = 1, k = 1 Output: [[1]] Explanation: There is 1 choose 1 = 1 total combination.

Constraints

  • 1 <= n <= 20
  • 1 <= k <= n

1. 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
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        pool = [i for i in range(1, n+1)]
        ans :List[List[int]] = []

        def dfs(startindex: int, currentlist: List):
            tmpcurrentlist = currentlist[:]
            
            if(len(currentlist) == k):
                ans.append(currentlist)
                return

            for i in range(startindex, len(pool)):
                dfs(i+1, tmpcurrentlist + [pool[i]])


        dfs(0, [])
        # print(ans)
        return ans
        
# test set
a = Solution()
a.combine(4, 2)

이전에 33번 문제 풀 때 기억을 살려서 먼저 append-return 조건부터 만들어놓고, 아래 for문에서 element가 중복되지 않게 넣으려면 index를 하나씩 뒤로 밀어서 for문으로 반복하면 되지 않을까 싶어서 했는데 1트만에 바로 됐다… 천잰가? 결과 자체만 놓고 보면 다른 코드와 비교해봤을 때 시간이 좀 많이 걸리긴 했다. 하지만 내 스스로, 그것도 1트만에 바로 accepted 받았다는 점에서 이미 감격…

2. My Own Solution #2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        ans = []

        def dfs(startindex: int, currentlist: List):
            if(len(currentlist) == k):
                ans.append(currentlist)
                return 

            for i in range(startindex, n):
                dfs(i+1, currentlist + [i+1])

        dfs(0, [])
        # print(ans)
        return ans

원래 한 번에 통과하고 넘어간 걸로 만족하려고 했는데 다른 예시 풀이보니까 나랑 다른게 없는데 왜 이렇게 차이가 많이날까 고민하다가 조금 뜯어고친 코드이다. 유일하게 달라진 점은 원래 코드에서는 다음 재귀로 넘어갈 때 list가 mutate 되지 않도록 tmpcurrentlist를 만들었었는데, 생각해보니 변형될만한 부분이 아무데도 없어서 그냥 지워버렸다. 아마 매번 재귀할때마다 복사하고 시작해서 시간을 좀 잡아먹지 않았나 싶다. 근데 그보다 한 10번 돌렸는데 속도가 상위 12%에서 60%까지 왤케 변동이 컸을까… 이건 잘 모르겠다. 여기서 하나 더 깨달은 점은 내 첫번째 풀이도 그렇게 나쁜 코드는 아니었다는 것이다 ㅎㅎ 뭔가 구조적으로 확 바뀌어야 속도 빨라지는 줄 알았는데 그건 아니더라.

3. Given Solution #1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
                dfs(elements, i+1, k-1)
                elements.pop()

        dfs([], 1, k)
        return results

책에 나온 풀이. 저번에 permutations 문제와 동일하게 재귀를 시작하는 시점을 영리하게 잘 잡아서 append한 상태로 보내고, 재귀 탈출하자마자 다시 pop시켜서 이후의 재귀에는 영향을 미치지 않도록 잘 설계한 풀이이다.

3. Given Solution #2

1
2
3
class Solution:
    def combine(self, nums: List[int]) -> List[List[int]]:
        return list(itertools.combinations(range(1, n+1), k))

저번 permutation 문제에서도 썼던 라이브러리. 설명 생략.

Overall Review

DFS를 배우기 전에는 참 막막했을 문제들이 알고리즘에 대해 배우면서 조금씩 풀리는게 쏠쏠한 재미를 주는 것 같다. 반복해서 풀다 보니까 안보이던 것도 보이게 되는 것 같기도 하고 말이다. 나중에는 어떤 알고리즘이 나올지 기대되기도 하고 재밌을 것 같다.