BOJ 단계별: 이분 탐색 https://www.acmicpc.net/step/29
06/02 화
-문제: https://www.acmicpc.net/problem/192
import sys
N = int(sys.stdin.readline())
A = set(map(int, sys.stdin.readline().split()))
M = int(sys.stdin.readline())
B = list(map(int, sys.stdin.readline().split()))
for i in B:
if i in A:
print(1)
else:
print(0)
set이 적합def solution(numbers):
str_num = str(numbers)
for i in str_num:
if i > i+1:
tmp = i+1
i+1 = i
i = tmp
answer = str_num
return answer
i + 1 같은 표현은 문자열 인덱스 Xdef solution(numbers):
str_num = list(map(str, numbers))
str_num.sort(key=lambda x: x*3, reverse=True)
answer = ''.join(str_num)
return answer
3 < 30 -> 303"3" > "30" -> 3303 > 34가 문자열 기준으로도 True이기 때문에 334가 됨 -> 343가 되어야 함3*2 -> 33해서 34*2 -> 3434 하면 33 < 34가 되기 때문에 -> 34(34)3(3) 가능key=lambda x:x*3: 문자열 비교를 기준으로 3번 반복한 상태로 설정reverse=True: 큰 수부터 나열| 개념 | 설명 |
|---|---|
| 문자열 비교 | "3" > "30" → 사전순 기준으로 판단함 |
| 커스텀 정렬 key | lambda x: x*3은 정렬 기준을 조작하는 핵심 |
| 정렬 방향 | 큰 숫자부터 이어붙이기 위해 reverse=True 사용 |
| 문자열 결합 | ''.join(list)로 결합, join() 대상은 문자열 리스트여야 함 |
| 숫자 → 문자열 | 숫자를 비교하기 전에 map(str, numbers)로 변환 필요 |
| 문자열 → 숫자 → 문자열 | "000" → int() → "0" 으로 변환하여 결과값 보정 |
#초기 코드
for i in check:
for j in cards:
if i == j:
res = 1
print(res)
break
print(res)
#중간 설계
count = [0 for i in range(M)]
for i in range(M):
for j in cards:
if j == check[i]:
count[i] += 1
print(*count)
#최종 설계
import sys
from collections import Counter
N = int(sys.stdin.readline()) #상근이 숫자 카드 개수
cards = list(map(int, sys.stdin.readline().split())) #숫자 카드에 적힌 정수
M = int(sys.stdin.readline()) #M개 숫자
check = list(map(int, sys.stdin.readline().split()))
#상근이가 몇 개 가지고 있는지 체크해야 할 숫자
counter = Counter(cards) #몇 개 가지고 있는지 카운팅
for i in check:
print(counter[i], end = ' ')
Counter(cards): cards 안의 숫자 등장 횟수를 딕셔너리 형태로 저장counter[x] -> 존재하면 갯수, 없으면 0 반환from collections import Counter로 불러옴#기본 사용법
from collections import Counter
data = ['a', 'b', 'b', 'c', 'a', 'b']
counter = Counter(data)
print(counter) # Counter({'b': 3, 'a': 2, 'c': 1})
counter['b'] -> 3counter['z'] -> 0#문자열 버전
text = "hello world"
counter = Counter(text)
print(counter) # 각 알파벳이 몇 번 나왔는지
#딕셔너리처럼 동작
print(counter['l']) # 3
print(counter.get('x', 0)) # 없는 키: 0
자주 쓰이는 메서드
| 메서드 | 설명 |
|---|---|
most_common(n) | 가장 많이 등장한 요소 n개를 반환 |
elements() | 요소들을 원래 개수만큼 반복해 리턴 (이터레이터) |
update(iterable) | 요소 개수를 더함 |
subtract(iterable) | 요소 개수를 뺌 |
사용하면 좋을 때
"이름": "김철수", "나이": 23person = {
"이름": "김철수",
"나이": 23,
"직업": "개발자"
}
#1. 생성
person = {} # 빈 딕셔너리
person = {"name": "Alice", "age": 30}
#2. 값 조회
print(person["name"]) # "Alice"
# 키가 없을 경우 오류 발생 -> 안전하게 쓰기 위해 get() 사용
print(person.get("height", "없음")) # "없음"
#3. 값 추가 및 수정
person["age"] = 31 # 값 수정
person["job"] = "developer" # 값 추가
# 삭제
del person["job"]
## 딕셔너리 반복문
for key in person:
print(key, person[key])
for key, value in person.items():
print(key, value)
set: 존재 유무만 판단하기 좋음Counter: 존재 유무 + 카운팅combinations 내장 함수 쓰면 될 것 같은데def solution(balls, share):
#조합 => nCr 하면 되는데
# n!/(n-r)!*r!
# 1. 직접 계산하거나 2. combination 쓰거나
# n!
sol_n = 1
for i in range(1, balls+1):
sol_n *= i
# n-r!
sol_nr = 1
for i in range(1, (balls-share)+1):
sol_nr *= i
# r!
sol_r = 1
for i in range(1, share+1):
sol_r *= i
answer = sol_n / (sol_nr * sol_r)
return answer
from itertools import combinations
def solution(balls, share):
comb = list(combinations(balls, share))
answer = len(comb)
return answer
balls는 정수 => combinations(n, r)에서 n은 iterable(리스트)여야 함range(balls)로 변환하면 체크 가능from itertools import combinations
def solution(balls, share):
comb = list(combinations(balls, share))
answer = len(comb)
return answer
from math import comb
def solution(balls, share):
return comb(balls, share)
itertools는 조합 자체를 생성 <- 처음에 틀린 이유math.comb() = 갯수만 계산combinations(iterable, r) 에서 iterable은 반드시 iterable(반복 가능 객체)여야 함iterable = 문자열, 리스트, 튜플, rangecombinations(5, 2) 같은 거 안 됨combinations(): 조합 결과를 실제로 꺼내서 활용하는 문제에 적합math.comb(n, r): 단순히 갯수 구하는 문제에 적합balls = 5range(5) = [0, 1, 2, 3, 4] 형태list X, range 타입 자체for문에 넣으면 하나씩 꺼낼 수 있음공통점
for)range(5)[2])len(range(5)))def solution(array):
answer = []
max_arr = max(array)
max_index = array.index(max_arr)
answer.append(max_arr)
answer.append(max_index)
return answer
from collections import Counter
def solution(array, n):
ch_n = chr(n) # ❌ 불필요한 문자 변환
counter = Counter(array)
answer = counter['ch_n'] # ❌ 문자열 'ch_n'이라는 키를 찾고 있음
return int(answer)
문자(열)만 카운팅해 주는 줄 알아서, 변환하려고 했으나 정수도 카운팅해 준다고 하여서 삭제counter[n] 이렇게 [],를 써야 함
from collections import Counter
def solution(array, n):
counter = Counter(array)
answer = counter[n]
return answer
def solution(array, n):
count_n = 0
for i in array:
if n == i:
count_n += 1
answer = count_n
return answer
collections.Counter는 내부적으로 dict을 상속한 자료구조로, 각 요소의 등장 횟수를 저장하는 **딕셔너리 기반 클래스++[] 대괄호로 키에 접근해야 함0 반환 (keyError 발생 안 함)문제: https://school.programmers.co.kr/learn/courses/30/lessons/42576?language=python3
participant 배열에서 이름을 하나씩 순회해서completion에 있다면 => 제거하자def solution(participant, completion):
for name in participant:
participant.remove(name)
return completion[0]
for name in participant로 순회하면서 participant.remove()를 동시에 할 경우 리스트를 순회하는 동작 중에 수정하게 되어 예기치 못한 동작이 발생completion을 사용하는 것도 빠트림collections.Counter를 써서 참가자와 완주자 각각의 요소를 카운팅하자from collections import Counter
def solution(participant, completion):
counter_p = Counter(participant) # 참가자 수 세기
counter_c = Counter(completion) # 완주자 수 세기
result = counter_p - counter_c # 미완주자만 남는다
answer = list(result.keys())[0] # key만 뽑아 첫 번째 값 반환
return answer
collections 모듈의 Counter는 딕셔너리처럼 작동Counter(["leo", "kiki", "eden"])
# => {'leo': 1, 'kiki': 1, 'eden': 1}
Counter1 - Counter2 => 겹치는 키의 값을 빼 줌Counter.keys(): 등장한 값만 추출Counter.values(): 각 값의 등장 횟수만 추출Counter.items(): (값, 쌍) 쌍result = counter_p - counter_c로 남는 값은 Counter 객체로 반환
# participant = ["leo", "kiki", "eden"]
# completion = ["eden", "kiki"] 라고 가정했을 때
result = Counter({'leo': 1})
result.keys() => `dict_keys(['leo']) 형태로 나오는데list()로 감싸 변환 필요list(result.keys()) # ['leo']
list(result.keys())[0] # 'leo'
.keys() -> list() -> [0] 순서로 변환 필요dict_keys라는 특수 객체를 우리가 일반적으로 사용하는 리스트처럼 다루기 위한 파이썬 관용 표현O(n) 탐색 반복 필요Counter는 내부적으로 딕셔너리(HashMap) 기반이라 탐색이 O(1)에 가까움Counter를 쓰는 것이 더 적합def solution(s):
lst = list(map(int, s.split()))
max_s = max(lst)
min_s = min(lst)
answer = []
answer.append(min_s)
answer.append(max_s)
return " ".join(answer)
.join() 함수 사용이 잘못된 것 같아서 수정 => join()은 문자열만 결합해 주기 때문에, map으로 answer을 str 타입으로 변환해 줘야 함def solution(s):
lst = list(map(int, s.split()))
max_s = max(lst)
min_s = min(lst)
answer = []
answer.append(min_s)
answer.append(max_s)
return " ".join(map(str, answer))
join() 함수는 문자열 리스트만 바꿀 수 있음return " ".join(map(str, answer))
combinations 내장 함수 써야지set 이용| 개념 | 설명 |
|---|---|
combinations(numbers, 2) | 리스트에서 두 개씩 뽑는 조합을 생성함 |
for a, b in comb: | 각 조합은 튜플 (a, b)이므로 언패킹 필요 |
set() | 중복 제거 |
sorted() | 오름차순 정렬 |
from itertools import combinations
def solution(numbers):
comb = list(combinations(numbers, 2)) # 2개씩 조합
comb_sum = []
for a, b in comb:
ab_sum = a + b
comb_sum.append(ab_sum)
answer = sorted(set(comb_sum)) # 중복 제거 + 정렬
return answer
for a, b in comb:
ab_sum = a + b
comb_sum.append(ab_sum)
answer = sorted(set(comb_sum)) # 중복 제거 + 정렬
return answer
#리스트 컴프리헨션
return sorted(set([a+b for a, b in conbinations(numbers,2)]))
[(a, b), (a, c), ...](a, b)를 분해해서 a,b로 받는 것def solution(d, budget):
count_num = 0 #가짓수 카운팅 변수
d.sort()
for i in d:
if budget < i: #d[i]가 예산보다 커지면
break
budget -=i #d[i]의 값에 예산 부여 후 budget 예산 차감
count_num += 1 #가짓수 카운팅
return count_num
N = int(input()) # 예산 요청하는 지방 수
lst = list(map(int, input().split())) #지방의 예산 요청
M = int(input()) # 총 예산
#설계
#mid를 구해서
#mid보다 작은 예산은 걍 더하고
#mid부터 큰 값은 다 mid로 바꿔서 더해서
#총액을 M이랑 비교
#M보다 작으면 다음 mid 구하고
#M보다 크면 이전 mid가 최댓값
lst.sort() #오름차순 정렬
mid = lst[N // 2] #중앙값
total = 0 # 예산 총합
#여기 총합보다 작은 경우는 그냥 하자
sum_lst = sum(lst)
if sum_lst <= M:
print(lst[-1])
else:
#이 부분은 무조건 애초에 총합이 M보다 클 경우로 조건문 때리고
while True:
for i in lst: #리스트 순회
# 만약 요청액이 mid보다 같거나 크면 -> mid값을 더하고
if i >= mid:
total += mid
else:
#만약 요청액이 mid보다 작으면 그냥 요청액을 더하고
total += i
#여기는 아직 while문 안에서 mid(상한값) 찾기 위한 조건이고
if total <= M:
mid = (mid + lst[-1]) // 2 #mid값을 조정
#그러면 다음 mid값을 구해야 되는데 여기에서 mid값 조정해서 다시 리스트 순회부터 반복해도 될 거 같은데?
#그러면 이제 while문 나오는 조건 어케 함? + 최댓값 설정해야 하는 코드 있어야 하는데
else:
break
print(mid)
N = int(input()) # 예산 요청하는 지방 수
lst = list(map(int, input().split())) #지방의 예산 요청
M = int(input()) # 총 예산
#설계
#mid를 구해서
#mid보다 작은 예산은 걍 더하고
#mid부터 큰 값은 다 mid로 바꿔서 더해서
#총액을 M이랑 비교
#M보다 작으면 다음 mid 구하고
#M보다 크면 이전 mid가 최댓값
lst.sort() #오름차순 정렬
start = 0
end = lst[-1]
answer = 0
#여기 총합보다 작은 경우는 그냥 하자
sum_lst = sum(lst)
if sum_lst <= M:
answer = int(lst[-1])
print(answer)
else:
#이 부분은 무조건 애초에 총합이 M보다 클 경우로 조건문 때리고
while start <= end:
total = 0
mid = (start + end) // 2
for i in lst: #리스트 순회
# 만약 요청액이 mid보다 같거나 크면 -> mid값을 더하고
if i >= mid:
total += mid
else:
#만약 요청액이 mid보다 작으면 그냥 요청액을 더하고
total += i
#여기는 아직 while문 안에서 mid(상한값) 찾기 위한 조건이고
if total <= M:
answer = mid
start = mid + 1 #mid 값 조정
#그러면 다음 mid값을 구해야 되는데 여기에서 mid값 조정해서 다시 리스트 순회부터 반복해도 될 거 같은데?
#그러면 이제 while문 나오는 조건 어케 함? + 최댓값 설정해야 하는 코드 있어야 하는데
else:
end = mid - 1
print(answer)
while True 종료 조건이 없었음total 값을 루프 밖에서 설정해서, 루프 안에서 초기화를 해야 하는데 누적이 됨mid = lst[N//2]로 설정start와 end 범위 전체를 기준으로 mid를 갱신해가며 조건을 만족하는 최댓값 찾기start와 end를 조정해서 범위 조정이 포인트start, end, mid 값을 이진 탐색 원리에 따라 생신answer 생신해서 정석적 이진 탐색 구조 만들었음total <= M은 현재 상한값(mid)가 가능 => 가능한 더 큰 상한값을 찾기 위해 start = mid + 1tosta > M이면 현재 상한값(mid) 불가능 => 더 작은 값에서 상한값 찾기 위해 end = mid - 1조건을 만족하는 최대/최소값 찾는 Parametic Search에서 자주 등장start, end, mid, 조건식 -> 조선을 만족하면 탐색 범위를 반으로 줄여서 반복start = mid + 1end = mid -1answer = midstart, end, mid 이진 탐색 설정: 이진 탐색을 위한 기본 변수 설정 => 문제에서는 H가 가능한 범위 설정mid를 기준으로 잘린 나무 양 계산: if i > mid => total += (i - mid)total >= M라면 정답 후보H = mid, start = m + 1 => 여기에서 answer에 해당하는 값 갱신end = mid - 1import sys
N, M = map(int, sys.stdin.readline().split())
#나무의 수 N, 나무의 길이 M
tree_height = list(map(int, sys.stdin.readline().split()))
#나무 높이 리스트(N개겠지?)
start = 0
end = max(tree_height)
#이진 탐색을 위한 기본 변수 설정
#문제: M미터 이상의 나무를 집에 가져가기 위한 H의 최댓값
H = 0 #절단기에 설정할 높이 => 이진 탐색 mid의 최댓값이 H의 정답이 될 것
#지금 헷갈리는 게 M-H의 값을 상근이가 집에 가져가는 것
#예제에서 M=7이면 그냥 잘린 나무 총액이 7이상이면 되는 거니까
#자르는 값(이게 MID)될 거고 잘린 나무를 M=7이랑 비교하면 될 거 같은데?
#나무가 20, 15, 10, 17이니까
#여기에서 이진 탐색 결과가 15가 나와서
#잘려서 가져갈 수 있는 나무가 5, 0, 0, 2가 되니까 합치면 M=7 만족
#그러면 예제를 보고 계산을 생각해 보자
#mid (start + end) // 2 해서 설정하고 이게 정답 후보
#즉, 정답을 탐색해야 됨
# mid를 가지고 계산을 해 봐야 함
# for i in tree_height: #자른 값 비교하기 위해 for문
# total은 여기에서 초기화해
# if i > mid -> 그러면 i - mid를 한 걸 total 더해
# 여기에 해당하지 않으면 total 그대로 둬
# for문 다 돌면 -> total이랑 M이랑 비교해
# total이 M보다 작으면 start = mid + 1 해서 다시 돌리고
# total이 M보다 크면 end = mid - 1 해서 돌리고
# 이걸 만족하는 값을 그대로 정답으로 출력
while start <= end:
total = 0 #M이랑 비교할 잘린 나무 길이의 총합
mid = (start + end) // 2 #이진 탐색 mid값이자 정답 후보
for i in tree_height: #자른 값 비교하기 위해 for문
if i > mid:
total += i - mid
if total >= M: #total이 M보다 작으면 start = mid + 1
H = mid #일단 조건을 만족하므로 answer에 mid를 저장
start = mid + 1
else:
end = mid - 1
sys.stdout.write(str(H))
input() -> sys.stdin.readline() print() -> `sys.stdout.write()if total < M -> if total >= M으로 변경 H = mid로 정답 후보 갱신S = input()
alp = list(range(97, 123))
for i in alp:
print(S.find(chr(i)))
T = int(input())
for i in range(T):
R, S = input().split()
R = int(R)
S = list(S)
lst = []
for i in S:
alp = i*R
lst.append(alp)
answer = ''.join(lst)
print(answer)
def solution(n):
lst = [] #약수 담는 리스트
for i in range(1, n+1):
if n % i == 0:
lst.append(i)
return sum(lst)
nA, nB = map(int, input().split())
A = list(map(int, input().split()))
B = list(map(int, input().split()))
nBbA = []
for i in range(nA):
if A[i] not in B: # B가 리스트인 경우, in 연산이 O(N)
nBbA.append(A[i])
nBbA.sort()
print(len(nBbA))
print(*nBbA)
B를 리스트로 설정 => A[i] not in B는 B를 매번 순회하여 검사 => 시간복잡도가 O(N^2)import sys
nA, nB = map(int, sys.stdin.readline().split())
A = list(map(int, sys.stdin.readline().split()))
B = set(map(int, sys.stdin.readline().split())) # ✅ set으로 변경
nBbA = []
for a in A:
if a not in B: # ✅ set lookup은 평균 O(1)
nBbA.append(a)
nBbA.sort()
print(len(nBbA))
print(*nBbA)
B를 set으로 설정해서 시간복잡도 줄임| 자료형 | in 연산 시간복잡도 |
|---|---|
list | O(N) (순차 탐색) |
set | O(1) (해시 기반) |
list: 하나하나 비교set: 해시로 존재 여부를 빠르게 판별alpha = list(input())
time_sum = 0
for i in alpha:
if i == 'A' or i == 'B' or i == 'C':
time_sum += 3
elif i == 'D' or i == 'E' or i == 'F':
time_sum += 4
elif i == 'G' or i == 'H' or i == 'I':
time_sum += 5
elif i == 'J' or i == 'K' or i == 'L':
time_sum += 6
elif i == 'M' or i == 'N' or i == 'O':
time_sum += 7
elif i == 'P' or i == 'Q' or i == 'R' or i == 'S':
time_sum += 8
elif i == 'T' or i == 'U' or i == 'V':
time_sum += 9
elif i == 'W' or i == 'X' or i == 'Y' or i == 'Z':
time_sum += 10
print(time_sum)
while True:
try:
print(input())
except EOFError:
break
mal = list(map(int, input().split()))
sol_mal = [1, 1, 2, 2, 2, 8]
answer = []
for i in range(6):
answer.append(sol_mal[i] - mal[i])
print(*answer)
n: 입국 심사 기다리는 사람 수times: 각 심사관이 1명 심사할 때 걸리는 시간n명이 모두 심사를 마치기까지 걸리는 최소 시간start = 1, end = n * max(times)mid 시간 안에 몇 명 처리할 수 있을까?n명 이상이면 충분한 것 -> 더 줄일 수 있는지 확인(end = mid - 1def solution(n, times):
start = 1
end = n * max(times)
answer = 0
while start <= end:
mid = (start + end) // 2
total_time = 0
for i in times:
if i >= mid:
total_time += mid
else:
total_time += i
if total_time < n * mid:
answer = total_time
start = mid + 1
else:
end = mid - 1
return answer
for time in times:
total_people += mid // time
| 비교 항목 | 잘못된 버전 | 올바른 버전 |
|---|---|---|
| 기준 | 시간 값을 더함 | 처리 인원을 더함 |
| 목표와의 연결성 | "시간을 얼마나 소모했는가" | "시간 안에 몇 명 처리 가능한가" |
| 논리 구조 | 잘못된 누적 시간 비교 | 이진 탐색 조건에 맞는 처리 인원 비교 |
def solution(n, times):
start = 1
end = n * max(times)
answer = end
while start <= end:
mid = (start + end) // 2
total_people = 0
for time in times:
total_people += mid // time
if total_people >= n:
answer = mid # 가능한 경우, 줄여보기
end = mid - 1
else:
start = mid + 1
return answer