[묘공단] 코딩테스트 스터디 9주차
이 글은 골든래빗 코딩 테스트 합격자 되기 파이썬 편의 12장 문제풀이입니다
백트래킹
백트래킹과 백트래킹 알고리즘 개념
깊이 우선 탐색, 너비 우선 탐색은 데이터를 전부 확인하는 방법이며 이를 완전 탐색이라고 함
완전 탐색은 모든 경우의 수를 탐색하는 방법이므로 비효율적
따라서 탐색을 하다가 가능성이 없다면 되돌아가고, 가능성이 있는 곳을 탐색하는 알고리즘을 백트래킹이라고 한다
백트래킹 알고리즘의 핵심은 '해가 될 가능성을 판단하는 것'이며 그것을 유망함수라는 것을 정의하여 판단함
- 유효한 해의 집합을 정의
- 위 단계에서 정의한 집합을 그래프로 표현
- 유망 함수를 정의
- 백트래킹 알고리즘을 활용해서 해를 찾음
예를 들어 1, 2, 3, 4 중 2개의 숫자를 뽑아서 6보다 큰 조합을 찾을 때 백트래킹을 활용한다면
유망 함수로는 처음 숫자가 3 미만이면 백트래킹한다라는 전략으로 접근 가능
또는 부분 집합의 합이 K가 되는 경우를 구할 때 완전 탐색은 포함/미포함 경우의 수로 봤을 때 2^n 이지만
백트래킹을 활용한다면 1) 합이 K가 되면 더 탐색하지 않고 2) 현재까지의 합이 K 이상이라면 더 탐색하지 않을 수 있음
몸풀기 문제
1부터 N까지 숫자 중 합이 10이 되는 조합 구하기
유망 함수의 조건
- 합이 10이 되는 경우 바로 return 및 결과에 추가
- 숫자의 합이 10보다 크면 백트래킹
0부터 시작해서 제일 하단의 backtrack은 아래와 같이 퍼지는 형태. 즉 유효한 해의 집합을 구성하는 코드 연습이 필요함
# [1] -> [1, 2]
# [1, 3]
# [1, 4]
# ...
# [1, N]
# [2] -> [2, 3]
# -> [2, 4]
# -> [2, 5]
# ...
# [3] -> [3, 4]
# ...
# ...
# [7] -> [7, 8]
# ...
from typing import List
def solution(N: int) -> List[List[int]]:
results = []
def backtrack(total_sum: int, path: List[int], start: int):
if total_sum == 10:
results.append(path)
return
for i in range(start, N + 1):
if total_sum + i <= 10:
backtrack(total_sum + i, path + [i], i + 1)
backtrack(0, [], 1)
return results
assert solution(5) == [[1, 2, 3, 4], [1, 4, 5], [2, 3, 5]]
assert solution(2) == []
assert solution(7) == [
[1, 2, 3, 4],
[1, 2, 7],
[1, 3, 6],
[1, 4, 5],
[2, 3, 5],
[3, 7],
[4, 6],
]
실전 문제
피로도
from itertools import permutations
from typing import List
def solution(k: int, dungeons: List[List[int]]) -> int:
answer = -1
for permutation in permutations([x for x in range(len(dungeons))]):
current_val = k
num_of_dungeon = 0
for index in permutation:
if current_val >= dungeons[index][0]:
num_of_dungeon += 1
current_val -= dungeons[index][1]
answer = max(answer, num_of_dungeon)
return answer
assert solution(80, [[80, 20], [50, 40], [30, 10]]) == 3
N-Queen
(0, 0)부터 시작해서 상태 트리를 그리고 구현에 옮기는 연습이 필요
현재 행 정보와, 체스판에 놓은 좌표를 들고 갱신하며 현재 행이 마지막 행까지 온 경우 퀸을 놓을 수 있는 경우이다
말판을 놓을 때 탐색에 들어가는 조건은 같은 열이 아니고, 대각선에 위치하지 않을 때만 탐색에 들어간다
from dataclasses import dataclass
from typing import List
@dataclass
class Coordinate:
row: int
col: int
def solution(n: int) -> int:
answer = []
def search(row: int, coordinates: List[Coordinate]):
if row == n:
answer.append(1)
return
for col in range(n):
early_return = False
for coord in coordinates:
prev_row, prev_col = coord.row, coord.col
if prev_col == col or abs(prev_col - col) == abs(prev_row - row):
early_return = True
break
if not early_return:
search(row + 1, coordinates + [Coordinate(row, col)])
search(0, [])
return len(answer)
assert solution(4) == 2
양궁대회
시간 초과 코드, 어피치의 사전을 구성하고, Ryan의 사전을 가능한 경우의 수에 따라 계산하는 방식인데
중간에 백트래킹이나 특정 부분이 잘못 구현된 것으로 보임
파이썬을 사용할 때는 특별히 제한이 없는 한 표준 라이브러리를 활용하자
import copy
import math
from collections import defaultdict
from typing import Sequence, List, Mapping
def solution(n: int, info: Sequence[int]) -> List[int]:
answer = [-1]
score_tracking = [-math.inf]
apeach_score_counter = {}
for index, value in enumerate(info):
apeach_score_counter[10 - index] = value
def calculate_scores(ryan_score_counter: Mapping[int, int]):
_apeach_score = 0
_ryan_score = 0
for i in range(1, 10 + 1):
if ryan_score_counter[i] > apeach_score_counter[i]:
_ryan_score += i
elif ryan_score_counter[i] < apeach_score_counter[i]:
_apeach_score += i
elif (
ryan_score_counter[i] == apeach_score_counter[i]
and ryan_score_counter[i] != 0
):
_apeach_score += i
else:
continue
return _apeach_score, _ryan_score
def search(num_shoot: int, ryan_score_counter: Mapping[int, int]):
if num_shoot == n:
# 어피치, 라이언 점수 계산
# 라이언이 이기면서 이점 점수 차보다 크거나 같으면 저장
apeach_score, ryan_score = calculate_scores(ryan_score_counter)
if ryan_score > apeach_score:
if ryan_score - apeach_score > score_tracking[0]:
score_tracking[0] = ryan_score - apeach_score
answer[0] = copy.deepcopy(ryan_score_counter)
elif ryan_score - apeach_score == score_tracking[0]:
for i in range(11):
if ryan_score_counter[i] > apeach_score_counter[i]:
answer[0] = copy.deepcopy(ryan_score_counter)
break
return
for score in range(10, -1, -1):
apeach_score, ryan_score = calculate_scores(ryan_score_counter)
if (
ryan_score > apeach_score
and ryan_score_counter[score] > apeach_score_counter[score]
):
continue
ryan_score_counter[score] += 1
if answer[0] != ryan_score_counter:
search(num_shoot + 1, ryan_score_counter)
ryan_score_counter[score] -= 1
search(0, defaultdict(int))
decision = answer[0]
if decision == -1:
return [-1]
else:
output = []
for score in range(10, -1, -1):
output.append(decision[score])
return output
표준 라이브러리를 사용한 더 간결한 풀이
from collections import Counter, defaultdict
from itertools import combinations_with_replacement
from typing import Sequence, List, Mapping
def solution(n: int, info: Sequence[int]) -> List[int]:
def calculate_scores(ryan_score_counter: Mapping[int, int]):
_apeach_score, _ryan_score = 0, 0
for score in range(11):
if ryan_score_counter[score] > apeach_score_counter[score]:
_ryan_score += score
elif apeach_score_counter[score] > 0:
_apeach_score += score
return _ryan_score, _apeach_score
def convert_to_list(counter: Mapping[int, int]) -> List[int]:
result = []
for score in range(10, -1, -1):
result.append(counter[score])
return result
apeach_score_counter = Counter()
for index, value in enumerate(info):
apeach_score_counter[10 - index] = value
max_diff = 0
answer = defaultdict(int)
for combination in combinations_with_replacement(range(11), n):
current_score_counter = Counter(combination)
ryan_score, apeach_score = calculate_scores(current_score_counter)
if ryan_score - apeach_score > max_diff:
max_diff = ryan_score - apeach_score
answer = current_score_counter
return [-1] if not answer else convert_to_list(answer)
assert solution(5, [2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]) == [
0,
2,
2,
0,
1,
0,
0,
0,
0,
0,
0,
]
assert solution(1, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) == [-1]
assert solution(9, [0, 0, 1, 2, 0, 1, 1, 1, 1, 1, 1]) == [
1,
1,
2,
0,
1,
2,
2,
0,
0,
0,
0,
]
assert solution(10, [0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 3]) == [
1,
1,
1,
1,
1,
1,
1,
1,
0,
0,
2,
]
외벽 점검
weak 자료구조를 확장해서 시계, 반시계를 시계만 고려하게 문제 조건을 완화하고,
담당할 수 있는 친구를 어떻게 경우의 수 처리를 할 지 (순열)를 캐치해야 풀 수 있는 문제
구현 난이도도 사실 쉽지는 않다, 커버 불가능하면 인덱스 연산을 갱신하는 부분 등
import math
from itertools import permutations
from typing import List
def solution(n: int, weak: List[int], dist: List[int]) -> int:
num_of_weak_points = len(weak)
weak.extend([x + n for x in weak])
answer = math.inf
# 1, 5, 6, 10, 13, 17, 18, 22
# 1, 5, 6, 10
# 5, 6, 10, 13
# 6, 10, 13, 17
# 10, 13, 17, 18
for i in range(num_of_weak_points):
for friend_order in permutations(dist):
friend_count = 1
position = weak[i] + friend_order[friend_count - 1]
for j in range(i, i + num_of_weak_points):
if position < weak[j]:
friend_count += 1
if friend_count > len(dist):
break
position = weak[j] + friend_order[friend_count - 1]
answer = min(answer, friend_count)
return answer if answer <= len(dist) else - 1
assert solution(12, [1, 5, 6, 10], [1, 2, 3, 4]) == 2
assert solution(12, [1, 3, 4, 9, 10], [3, 5, 7]) == 1
사라지는 발판
프로그래머스 풀이가 더 직관적이어서 링크로 대체, 추후에 다시 풀어볼 것
댓글
이 글 공유하기
다른 글
-
[묘공단] 코딩테스트 스터디 11주차
[묘공단] 코딩테스트 스터디 11주차
2024.02.07 -
[묘공단] 코딩테스트 스터디 10주차
[묘공단] 코딩테스트 스터디 10주차
2024.02.03 -
[묘공단] 코딩테스트 스터디 8주차
[묘공단] 코딩테스트 스터디 8주차
2024.01.19 -
[묘공단] 코딩테스트 스터디 7주차
[묘공단] 코딩테스트 스터디 7주차
2024.01.12