파이썬으로 구현하는 조합과 순열 총정리

YooJeeun·2025년 4월 4일

알고리즘

목록 보기
1/2

알고리즘 문제를 풀다 보면 "조합", "순열", 그리고 "중복 처리"를 요구하는 경우가 자주 나온다.
이 글에서는 파이썬을 사용해서 조합과 순열의 다양한 경우(총 8가지)를 모두 정리해보았다.

1. 일반 순열

순서가 있고 중복이 안됨 -> [1, 2, 3]중에 2개씩 선택하는 경우
12, 13, 21, 23, 31, 32

✅ DFS를 이용해 직접 구현한 일반 순열

import sys
inp = sys.stdin.readline()

n, m = map(int, inp.split())
arr = []
for i in range(n):
    arr.append(i+1)

result = []
visited = [False]*n

def permut():
    if len(result) == m: 
        print(result)
        return
        
    for i in range(n):
        if visited[i]: continue
        visited[i] = True
        result.append(arr[i])
        permut()
        visited[i] = False
        result.pop()
        
permut()

✅ itertools를 사용해 구현한 일반 순열

from itertools import permutations

data = [1, 2, 3]
result = list(permutations(data, 2))
print(result)

# 출력: [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]

⏰시간복잡도

O(P(n, m)) = O(n! / (n - m)!)
첫 번째 자리에 n개 중 1개 -> n
두 번째 자리에 n-1개 중 1개 -> n-1
...m개 뽑을 때 까지 곱하면 -> n × (n-1) × ... × (n-m+1) = P(n, m)


2. 일반 조합

순서가 없고 중복이 안됨 -> [1, 2, 3]중에 2개씩 선택하는 경우
12, 13, 23

✅ DFS를 이용해 직접 구현한 일반 조합

n, m = map(int, inp.split())
arr = [i+1 for i in range(n)]

result = []
def comb(idx):
    if len(result) == m:
        print(result)
        return

	for i in range(idx, n):
        result.append(arr[i])
        comb(i+1)
        result.pop()

comb(0)

✅ itertools를 사용해 구현한 일반 조합

from itertools import combinations

data = [1, 2, 3]
result = list(combinations(data, 2))
print(result)

# 출력: [(1, 2), (1, 3), (2, 3)]

⏰시간복잡도

O(C(n, m)) = O(n! / (m!(n-m)!))
순열에서 중복되는 값을 없애야 하므로 -> P(n, m) / m!


3. 중복 순열

순서가 있고 중복이 가능 -> [1, 2, 3]중에 2개씩 선택하는 경우
11, 12, 13, 21, 22, 23, 31, 32, 33

✅ DFS를 이용해 직접 구현한 중복 순열

n, m = map(int, inp.split())
arr = [i+1 for i in range(n)]

result = []
def dup_permut():
    if len(result) == m:
        print(result)
        return
    
    for i in range(n):
        result.append(arr[i])
        dup_permut()
        result.pop()
        
dup_permut()

✅ itertools를 사용해 구현한 중복 순열

from itertools import product

data = [1, 2, 3]
result = list(product(data, repeat=2))
print(result)

# 출력: [(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3), (3, 1), (3, 2), (3, 3)]

⏰시간복잡도

O(n^m)
각 자리마다 n가지를 m번 가능 -> n × n × ... × n = n^m


4. 중복 조합

순서가 없고 중복이 가능 -> [1, 2, 3]중에 2개씩 선택하는 경우
11, 12, 13, 22, 23, 33

✅ DFS를 이용해 직접 구현한 중복 조합

n, m = map(int, inp.split())
arr = [i+1 for i in range(n)]
result = []

def dup_comb(idx):
    if len(result) == m:
        print(result)
        return
    
    for i in range(idx, n):
        result.append(arr[i])
        dup_comb(i)
        result.pop()

dup_comb(0)

✅ itertools를 사용해 구현한 중복 조합

from itertools import combinations_with_replacement

data = [1, 2, 3]
result = list(combinations_with_replacement(data, 2))
print(result)

# 출력: [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]

⏰시간복잡도

O(C(n + m - 1, m)) = O((n + m - 1)! / (m!(n-1)!))
서로 다른 원소 n개 중 중복을 허용하여 m개 선택 -> 중복 조합 공식 사용
[1,2,3]에서 2개를 중복 허용 조합 → C(3 + 2 - 1, 2) = C(4,2)


5. 중복값이 존재하는 일반 순열

순서가 있고 중복값이 존재하는 일반 순열 -> [1, 2, 3, 3]중에 2개를 선택하는 경우
12, 13, 21, 23, 31, 32, 33

✅ DFS를 이용해 직접 구현한 중복값이 존재하는 일반 순열

(※ itertools로는 구현 불가)

m = 2
arr = [1, 2, 3, 3]
result = []
visited = [False]*len(arr)
def dup_num_permut():
    if len(result) == m:
        print(result)
        return
    
    tmp = 0

    for i in range(len(arr)):
        if tmp == arr[i] or visited[i]: continue
    
        result.append(arr[i])
        tmp = arr[i]
        visited[i] = True
        dup_num_permut()
        result.pop()
        visited[i] = False
        
dup_num_permut()

6. 중복값이 존재하는 일반 조합

순서가 없고 중복값이 존재하는 일반 조합 -> [1, 2, 3, 3]중에 2개를 선택하는 경우
12, 13, 23, 33

✅ DFS를 이용해 직접 구현한 중복값이 존재하는 일반 조합

(※ itertools로는 구현 불가)

m = 2
arr = [1, 2, 3, 3]
result = []
def dup_num_comb(idx):
    if len(result) == m:
        print(result)
        return
    
    tmp = 0

    for i in range(idx, len(arr)):
        if tmp == arr[i]: continue
        result.append(arr[i])
        tmp = arr[i]
        dup_num_comb(i+1)
        result.pop()

dup_num_comb(0) 

7. 중복값이 존재하는 중복 순열

순서가 있고 중복 값이 존재하는 중복 순열 -> [1, 2, 3, 3]중에 2개를 선택하는 경우
11, 12, 13, 21, 22, 23, 31, 32, 33

✅ DFS를 이용해 직접 구현한 중복값이 존재하는 중복 순열

(※ itertools로는 구현 불가)

m = 2
arr = [1, 2, 3, 3]
result = []
def ddup_permut():
    if len(result) == m:
        print(result)
        return
    
    tmp = 0
    for i in range(len(arr)):
        if tmp == arr[i]: continue
        result.append(arr[i])
        tmp = arr[i]
        ddup_permut()
        result.pop()
        
ddup_permut()

8. 중복값이 존재하는 중복 조합

순서가 없고 중복 값이 존재하는 중복 조합 -> [1, 2, 3, 3]중에 2개를 선택하는 경우
11, 12, 13, 22, 23, 33

✅ DFS를 이용해 직접 구현한 중복값이 존재하는 중복 조합

(※ itertools로는 구현 불가)

m = 2
arr = [1, 2, 3, 3]
result = []
def ddup_comb(idx):
    if len(result) == m:
        print(result)
        return
    
    tmp = 0
    for i in range(idx, len(arr)):
        if tmp == arr[i]: continue

        result.append(arr[i])
        tmp = arr[i]
        ddup_comb(i)
        result.pop()
        
ddup_comb(0)

⏰시간복잡도

중복 값이 존재하는 경우에 같은 조합/순열과 시간복잡도가 동일하지만 visited, tmp로 걸러주기 때문에 경우의 수 자체는 줄어듦


조합과 순열의 분류

중복 허용순서 고려종류
❌✅일반 순열
❌❌일반 조합
✅✅중복 순열
✅❌중복 조합
❌✅중복값이 존재하는 일반 순열
❌❌중복값이 존재하는 일반 조합
✅✅중복값이 존재하는 중복 순열
✅❌중복값이 존재하는 중복 조합

조합과 순열의 핵심 차이

일반 순열/조합 차이
일반 순열은 순서가 존재하기 때문에 for i in range(n)를 사용하는데, 이때 같은 값이 두번 들어가는 것을 방지하기 위해 visited 배열이 필요하다.
일반 조합의 경우는 순서가 없기 때문에 for i in range(idx, n) 처럼 인덱스가 커지는 경우만 존재하기 때문에 visited 배열이 필요 없다.

일반 순열/조합과 중복 순열/조합의 차이
일반은 중복이 허용되지 않기 때문에 일반 순열의 경우는 visited를 이용해서, 일반 조합은 comb(i+1)처럼 현재 인덱스+1한 값을 매개변수로 넣어준다.
중복은 중복이 허용되기 때문에 중복 순열의 경우는 visited 배열을 없애주고, 중복 조합은 comb(i)를 넣어주면 된다.

'중복값이 존재'와 '중복'의 차이
중복값이 존재한다는 건 arr[1, 2, 3, 3]처럼 배열 내 중복 값이 존재한다는 거고, 중복이 가능하다는건 그 배열 내의 요소로 가능한 조합을 뽑을 경우 해당 요소를 여러 번 사용할 수 있다는 것이다.(예: 1을 두 번 사용해서 11을 만들 수 있음)

profile
제니벨로그

0개의 댓글