백트래킹 문제
<15649번 : N과 M(1)>
import sys
input = sys.stdin.readline
def backtracking() :
# 원하는 길이의 순열이 완성되면 출력
if len(array) == m:
print(" ".join(map(str, array))) # 리스트의 원소를 공백으로 구분된 문자열로 변환 후 출력
return
# i는 1부터 N까지의 숫자
for i in range(1, n+1):
# 순열이므로 겹치는지 아닌지 확인
# 중복이 아니면 숫자 i를 리스트에 추가
if i not in array:
array.append(i)
backtracking()
# return 되어서 돌아오면 전 단계로 돌아감
# 예를 들어 순열이 1,2,3이면 return 되어서 돌아온 후 3이 pop되고
# 1,2에서 다음 값을 찾는 형식으로 전 단계로 돌아가는 것
array.pop()
n,m = map(int, input().split())
array=[]
backtracking()
☆ 백트래킹은 '완전탐색(Brute Force)'을 하면서 조건을 만족하지 않으면 탐색을 중단하는 방식
☆ for문 설명
- 1부터 N까지의 숫자를 반복문을 통해 확인
- 만약 숫자 i가 array 리스트에 존재하지 않는다면(if i not in array), i를 추가
- 이후, backtracking()을 재귀적으로 호출하여 다음 숫자를 선택
- 재귀 호출이 끝난 후에는 pop()을 사용하여 마지막으로 추가한 숫자를 제거하여, 원래 상태로 되돌린다.
▽ 백트래킹을 사용한 이유
1. 모든 경우의 수를 탐색하면서도, 불필요한 탐색은 하지 않음 → 중복을 방지하기 위해 'if i not in array:'로 이미 사용된 숫자는 선택하지 않음
2. DFS(깊이 우선 탐색)방식으로 해결 가능 → 한 번 선택한 숫자는 다음 재귀 호출에서 사용되지 않도록 조절하여 모든 경우를 탐색함
3. 재귀를 이용해 자연스럽게 수열을 생성하고, 백트래킹으로 돌아갈 수 있음 → array.pop()을 사용하여 이전 상태로 되돌아가면서 새로운 조합을 탐색함
▽ 시간 복잡도
- 백트래킹을 이용한 순열 생성 알고리즘 → 시간 복잡도는 O(N!)
▽ set을 사용하여 중복 확인한 최적화된 코드 → 시간 복잡도 O(1)
import sys
input = sys.stdin.readline
def backtracking():
if len(array) == m:
print(" ".join(map(str, array)))
return
for i in range(1, n + 1):
if i not in used: # ✅ set을 이용한 중복 검사 (O(1))
array.append(i)
used.add(i) # ✅ set에 추가
backtracking()
array.pop()
used.remove(i) # ✅ set에서 제거 (백트래킹)
n, m = map(int, input().split())
array = []
used = set() # ✅ 중복 확인을 위한 set
backtracking()
풀이
[백준 15649] N과 M (1) - 파이썬 with 백트래킹
https://www.acmicpc.net/problem/15649백트래킹 알고리즘의 기본 예제이다. 백트래킹은 DFS를 사용하여 풀 수 있는데, 일반적인 DFS와의 차이점은 가지치기를 한다는 점이다. 모든 경우의 수를 탐색하는 대
velog.io
<15650번 : N과 M(2)>
n,m = list(map(int, input().split()))
s = []
def f(start):
if len(s) == m:
print(' '.join(map(str,s)))
return
for i in range(start, n+1):
if i not in s:
s.append(i)
f(i+1)
s.pop()
f(1)
- start인자 추가 : start 변수를 사용하여 재귀 호출 시 다음에 선택할 수 있는 숫자의 범위를 제한한다. 이렇게 하면 오름차순을 유지하면서 수열 생성 가능
<15651번 : N과 M(3)>
import sys
input = sys.stdin.readline
def backtracking() :
if len(arr) == m:
print(*arr)
return
for i in range(1,n+1):
arr.append(i)
backtracking()
arr.pop()
n,m = map(int, input().split())
arr=[]
backtracking()
- 15649번과 동일하지만 중복을 허용하기 때문에, for문에서 중복을 확인하는 if문이 삭제됨
[다른 풀이]
n, m = map(int, input().split())
s = []
def dfs():
if len(s) == m:
print(' '.join(map(str,s)))
return
for i in range(1,n+1):
s.append(i)
dfs()
s.pop()
dfs()
<15652번 : N과 M(4)>
import sys
input = sys.stdin.readline
# 비내림차순..
def backtracking(x) : # 앞의 숫자와 비교해야하므로 파라미터로 넘겨줌
if len(arr) == m: # array의 길이가 m과 같으면 답 출력
print(*arr)
return
for i in range(x,n+1): # 파라미터로 받은 숫자보다 같거나 커야함
arr.append(i) # 수를 더해주고
backtracking(i) # 더한 수를 파라미터로 가지고 backtracking
arr.pop() # 원 상태로 돌리기 위해 pop
n,m = map(int, input().split())
arr=[]
backtracking()
- 비내림차순은 같을 때를 포함하는 개념. 오름차순은 확실히 다음 항이 더 커야한다.
- 현재 함수에서는 for문에서 넣은 숫자보다 같거나 커야한다. 파라미터로 넘겨주어서 체크해준다.
- 파라미터로 받은 숫자보다 같거나 커야하기 때문에 x이상 n이하
- 수를 더해주고 더한 수를 파라미터로 가지고 함수 호출
- 원상태로 돌리기 위해 pop
[다른 풀이]
n,m = list(map(int,input().split()))
s = []
def f(start):
if len(s) == m:
print(' '.join(map(str,s)))
return
for i in range(start,n+1):
s.append(i)
f(i) # 그냥 오름차순 문제랑 이 부분이 다름!
s.pop()
f(1)
<14888번 : 연산자 끼워넣기>
N = int(input())
lst = list(map(int, input().split()))
operators = list(map(int, input().split()))
mx = -1e9
mn = 1e9
def dfs(n, temp):
global mx, mn
# 종료 조건
if n == N-1:
mx = max(temp, mx)
mn = min(temp, mn)
return
# 하부함수 호출
if operators[0] != 0: # 덧셈하는 경우
operators[0] -= 1
dfs(n+1, temp + lst[n+1])
operators[0] += 1
if operators[1] != 0: # 밸셈하는 경우
operators[1] -= 1
dfs(n+1, temp - lst[n+1])
operators[1] += 1
if operators[2] != 0: # 곱셈하는 경우
operators[2] -= 1
dfs(n+1, temp * lst[n+1])
operators[2] += 1
if operators[3] != 0: # 나눗셈하는 경우
operators[3] -= 1
dfs(n + 1, int(temp/lst[n+1]))
operators[3] += 1
dfs(0, lst[0])
print(mx)
print(mn)
- 이 문제는 연산자의 우선순위가 반영되지 않고 순서대로 연산되기 때문에 백트래킹을 사용할 수 있다.
- 출력 범위가 주어졌으므로 범위를 꼭 지정해 줘야함
- 1e9 = 1*109 = 1000000000,
2e9 = 2*109 = 2000000000
- 문제에서 음수를 양수로 나눌 때는 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼다. → 파이썬의 int()가 해줌!!
* 파이썬은 우리가 기존에 알던 계산법과 음수 나눗셈 연산을 다르게 한다. 몫 연산시, 음수쪽으로 값을 내리기 때문에 더 작은 수가 나오게 된다.
- 잘 정리된 블로그
[BOJ] #14888. 연산자 끼워넣기 (Python)
링크: https://www.acmicpc.net/problem/14888유형: 백트래킹, 브루트 포스(완전탐색)작성일시: 2022년 10월 28일 오후 7:23N개의 수로 이루어진 수열 A1, A2, ..., AN이 주어진다. 또, 수와 수 사이에 끼워넣을
velog.io