파이썬 - 백준 문제 풀이(1)

‎육란·2023년 11월 6일

파이썬 알고리즘

목록 보기
6/8
post-thumbnail

업로드중..


오늘은 그동안 풀어왔던 백준 문제를 조금 정리해보려 한다.
문제를 많이 풀면서 이것 저것 찾아보고 했더니 머릿속에 정보가 너무 많아서 정리가 필요하다..



2108번: 통계학 - 실버Ⅲ


문제

수를 처리하는 것은 통계학에서 상당히 중요한 일이다. 통계학에서 N개의 수를 대표하는 기본 통계값에는 다음과 같은 것들이 있다. 단, N은 홀수라고 가정하자.
산술평균 : N개의 수들의 합을 N으로 나눈 값
중앙값 : N개의 수들을 증가하는 순서로 나열했을 경우 그 중앙에 위치하는 값
최빈값 : N개의 수들 중 가장 많이 나타나는 값
범위 : N개의 수들 중 최댓값과 최솟값의 차이
N개의 수가 주어졌을 때, 네 가지 기본 통계값을 구하는 프로그램을 작성하시오.


나의 코드

#2108번
import sys
import statistics
from collections import Counter
n = int(sys.stdin.readline()) # n개
num = [] # 숫자 리스트
sum = 0

for _ in range(n):
    a = int(sys.stdin.readline())
    sum += a
    num.append(a)

print(round(sum/n)) #산술평균
print(statistics.median(num)) #중앙값


counter = Counter(num)
most_common = counter.most_common()  # 가장 빈도가 높은 값

most_common_values = [x[0] for x in most_common if x[1] == most_common[0][1]]

if len(most_common_values) > 1:
    most_common_values.sort()
    print(most_common_values[1])
else:
    print(most_common_values[0])

print(max(num) - min(num)) #범위

해설

우선, 이 문제를 풀면서 처음 보는 모듈을 많이 사용하였다.
바로 statistics 모듈과 Counter 모듈이다.

첫 번째로 산술평균을 구할 때, 소수점 첫째 자리에서 반올림을 해야 하므로 반올림 하기 전의 결과가 실수형으로 나와야 한다.
그래서 // 나눗셈 대신 / 나눗셈을 사용하고, round() 연산자를 사용하였다.

두 번째로는 중앙값 구하기인데, 이 부분은 statistics 모듈을 사용하면 쉽게 구할 수 있다.
모듈에 있는 median() 함수를 사용하였다.

세 번째로 최빈값 구하기인데, 이게 가장 까다로웠다.
문제에서 빈도수가 같을 때 두 번째로 작은 값을 구하라고 했기 때문이다.
우선 Counter 모듈을 사용하여 most_common_values에 빈도수가 가장 높은 숫자들의 리스트를 생성하였다.
그리고 작은 값을 구하기 위해 sort() 함수로 최빈값 리스트를 정렬하였다.
그리고 두 번째로 작은 값인 most_common_values[1] 을 출력하였다.
물론 빈도수가 가장 높은 값이 1개뿐일 경우에는 그대로 출력하면 된다.

네 번째로는 범위인데, 이부분은 쉽다.
리스트의 max() 값과 min() 값의 차를 구해주었다.



10773번: 제로 - 실버 Ⅳ


문제

나코더 기장 재민이는 동아리 회식을 준비하기 위해서 장부를 관리하는 중이다.
재현이는 재민이를 도와서 돈을 관리하는 중인데, 애석하게도 항상 정신없는 재현이는 돈을 실수로 잘못 부르는 사고를 치기 일쑤였다.
재현이는 잘못된 수를 부를 때마다 0을 외쳐서, 가장 최근에 재민이가 쓴 수를 지우게 시킨다.
재민이는 이렇게 모든 수를 받아 적은 후 그 수의 합을 알고 싶어 한다. 재민이를 도와주자!


나의 코드

import sys
myque = []
k = int(sys.stdin.readline())

for _ in range(k):
    n = int(sys.stdin.readline())
    if(n!=0):
        myque.append(n)
    else:
        myque.pop()

sum=0
for i in range(len(myque)):
    sum = sum + myque[i]

print(sum)

해설

이 문제는 리스트에 원소를 넣고, 0이 들어왔을 때 가장 마지막 원소를 삭제하는 문제이므로 stack를 사용하면 간단하게 풀 수 있다.

먼저 큐 리스트를 만들어주고, 0이 아니라면 리스트에 수를 넣어준다.
만약 들어온 수가 0이라면, 리스트의 마지막 숫자를 pop한다.

마지막에는 간단하게 for문 을 이용해서 sum을 구해주면 된다.



15828번: Router - 실버 Ⅳ


문제

인터넷을 사용하기 위해서는 컴퓨터에 인터넷 회선을 연결하거나 Wi-Fi를 연결해야 한다. 이렇게 연결된 네트워크를 통해 컴퓨터에는 통신이 가능하다. 마음에 드는 노래나 동영상이 있는 곳에 파일을 전송해달라는 요청을 보내고 파일을 받는 식으로 말이다.
우리가 보낸 요청은 어떻게 목적지까지 도달하는 것일까? 컴퓨터에서는 패킷이라고 하는 형태로 정보를 주고 받는다. 네트워크의 유저들은 1:1로 연결되어 있지 않으므로, 일반적으로 패킷은 라우터라는 장비를 여러 번 거친다. 그러면 라우터에서는 패킷을 다른 라우터로 보내거나, 만약 목적지와 직접적으로 연결되어 있다면 그곳으로 보낼 수도 있다. 즉, 택배 회사의 물류 센터와 비슷한 역할을 한다고 보면 된다.
라우터 내부를 들여다보면 처리해야 할 패킷을 임시적으로 보관하기 위한 버퍼가 존재한다. 이 버퍼에는 라우터에 입력으로 들어온 패킷들이 순서대로 위치하고, 라우터에서는 먼저 온 패킷부터 하나씩 처리한 후 버퍼에서 제거한다. 만약 라우터가 패킷을 처리하는 속도보다 패킷이 들어오는 속도가 더 빠를경우 버퍼가 꽉 차거나 넘쳐버릴 것이다. 그렇게 되면 버퍼에 공간이 생길 때까지 입력받는 패킷은 모두 버려진다.
통신의 원리를 배웠으니까 간단하게 라우터의 작동 원리를 구현해보자. 물론 하나의 라우터만 존재한다고 가정하며, 우리가 다룰 부분은 라우터의 입출력이 주어졌을 때 버퍼의 상태가 어떻게 변하는가이다. 그러니까 라우터가 패킷을 구체적으로 어떤 방식으로 처리하고, 어디로 보내고 이런 것들은 생각하지 말자.

문제가 쓸데없이 거창하다..


나의 코드

import sys
myque = []
n = int(sys.stdin.readline()) #버퍼 사이즈
while True:
    k = int(sys.stdin.readline())
    if(k==-1): break
    if(k==0):
        myque.pop(0)
    elif(len(myque)<n):
        myque.append(k)

print(*myque)

해설

이 문제는 패킷을 순서대로 받고, 가장 먼저 들어온 패킷 순서대로 처리하는 문제이기 때문에 queue를 사용하면 쉽게 풀 수 있다.

주의할 점은 라우터가 존재하는 버퍼의 크기 n이 주어져 있기 때문에 이를 초과했을 경우 그 패킷은 버려야 한다.

따라서 while문에서 첫 번째 테스트로 만약 -1이 들어왔다면 while문을 종료한다.

두 번째 테스트로 0이 들어왔다면 첫 번째 원소(가장 먼저 들어온 원소)를 pop 한다.

그리고 0-1이 아닌 숫자가 들어왔을 경우, que 의 사이즈가 버퍼 사이즈보다 작다면, que에 숫자를 append 해준다.



2161번: 카드1 - 실버 Ⅴ


문제

N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다.
이제 다음과 같은 동작을 카드가 한 장 남을 때까지 반복하게 된다. 우선, 제일 위에 있는 카드를 바닥에 버린다. 그 다음, 제일 위에 있는 카드를 제일 아래에 있는 카드 밑으로 옮긴다.
예를 들어 N=4인 경우를 생각해 보자. 카드는 제일 위에서부터 1234 의 순서로 놓여있다. 1을 버리면 234가 남는다. 여기서 2를 제일 아래로 옮기면 342가 된다. 3을 버리면 42가 되고, 4를 밑으로 옮기면 24가 된다. 마지막으로 2를 버리고 나면, 버린 카드들은 순서대로 1 3 2가 되고, 남는 카드는 4가 된다.
N이 주어졌을 때, 버린 카드들을 순서대로 출력하고, 마지막에 남게 되는 카드를 출력하는 프로그램을 작성하시오.


나의 코드

import queue

que = queue.Queue()
li = []
n = int(input())

for i in range(1, n+1): #거꾸로 순서로의 범위
    que.put(i)

for _ in range(n-1):
    q = que.get()
    li.append(q) # 버린 거 리스트에 추가
    p = que.get()
    que.put(p) # 큐 위에 올리기

print(*li, que.get())

해설

이 문제를 처음 봤을 땐 뭔 말인가 싶었는데, 하다보면 어찌저찌 된다.

쉽게 말하자면 쌓여있는 카드에서 젤 위의 카드를 버리고, 다음 거는 제일 아래로 옮긴다.
이를 카드가 모두 없어질 때까지 반복한다.

그냥 문제 그대로 코드를 짰다.

이번에는 카드를 제일 위에서도 꺼내야 하고, 제일 밑에도 넣어야 하기 때문에 deque 를 사용하였다.

먼저 put() 함수를 사용해서 카드를 차곡차곡 쌓아주었다.

그리고 for문에서는 get() 함수를 사용해서 제일 위에 있는 카드를 버렸다.
문제에서 버린 카드들도 따로 출력하라고 했으므로 리스트에 고이 넣어두었다.

그리고 또 get() 함수를 이용해서 꺼낸 다음에 이번에는 put() 함수를 사용해서 카드 젤 밑에 깔아둔다.

참고로 위 코드에서는 나의 편리를 위해 deque의 젤 마지막이 카드의 맨 밑이다.

for문에서 range의 범위가 n-1까지인 이유는 마지막에 카드 1장은 남아있어야 하기 때문이다.
그리고 버린 카드 리스트를 *를 이용해서 언패킹하여 출력하였다.



5636번: 생일 - 실버 Ⅴ


문제

어떤 반에 있는 학생들의 생일이 주어졌을 때, 가장 나이가 적은 사람과 가장 많은 사람을 구하는 프로그램을 작성하시오.
첫째 줄에 반에 있는 학생의 수 n이 주어진다. (1 ≤ n ≤ 100)
다음 n개 줄에는 각 학생의 이름과 생일이 "이름 dd mm yyyy"와 같은 형식으로 주어진다. 이름은 그 학생의 이름이며, 최대 15글자로 이루어져 있다. dd mm yyyy는 생일 일, 월, 연도이다. (1990 ≤ yyyy ≤ 2010, 1 ≤ mm ≤ 12, 1 ≤ dd ≤ 31) 주어지는 생일은 올바른 날짜이며, 연, 월 일은 0으로 시작하지 않는다.
이름이 같거나, 생일이 같은 사람은 없다.


나의 코드

#5635번
from datetime import datetime #날짜 모듈
n = int(input())
name = []
date = []

for i in range(n):
    n, dd, mm, yyyy = input().split()
    name.append(n)
    date.append(datetime(int(yyyy), int(mm), int(dd)))

print(name[date.index(max(date))])
print(name[date.index(min(date))])

#birth = [[] for _ in range(n)] #n행 배열 선언
#birth[i] = [name, dd, mm, yyyy] #i행에 이름, 일, 월, 년 추가

해설

이 문제는 datetime 모듈을 처음 사용해 보았다.
이름, 일, 월, 년을 split()으로 나눠서 입력을 받는다.

date 리스트에 datetime 함수를 사용하여 정수로 바꾼 연, 월, 일을 차례로 추가한다.

나이가 가장 적은 사람은 생일이 가장 늦은 사람 (datemax값),
나이가 가장 많은 사람은 생일이 가장 빠른 사람 (datemin값) 을 출력하면 된다.



9946번: 단어 퍼즐 - 브론즈 Ⅰ


문제

준하는 유치원에서 단어 퍼즐게임을 즐겨한다.
단어 퍼즐게임이란, 주어진 알파벳들을 섞어서 단어를 만드는 게임이다.
천재 준하는 알파벳을 임의로 조합하여, 사전과 매칭된 단어를 만드는 프로그램을 만들어 단어를 완성시켰다.
그러나 완성된 단어를 원장님에게 가져가려는 순간, 지나가던 강민이와 부딫혀서 단어조각을 땅에 떨어뜨리고 말았다.
준하는 어찌어찌 조각을 회수했지만, 순서는 뒤죽박죽이 되었고, 알파벳이 부족하거나 다른 알파벳이 섞였을 수도 있다.
준하가 처음에 완성한 단어와 나중에 회수한 알파벳들이 주어질 때,
준하가 알파벳을 제대로 회수했는지 안했는지 판단하는 프로그램을 만들어주자.


나의 코드

import sys

n = 1
while True:
    ans = sys.stdin.readline().strip()
    test = sys.stdin.readline().strip()
    if ans == "END":
        break
    if(len(ans)!=len(test)):
        print(f"Case {n}: different")
        n += 1
        continue
    for i in range(len(test)):
        c = test[i]
        if c in ans:
            index = ans.find(c)
            ans = ans[:index] + ans[index+1:]
        else:
            print(f"Case {n}: different")
            n += 1
            break
    if(ans == ''):
        print(f"Case {n}: same")
        n += 1

해설

이 문제는 어떻게 보면 단순하지만, 실수를 많이 해서 좀 오래 걸렸다.
기본적으로는 무한 while문로 원래의 단어와 회수한 단어를 입력 받는다.

첫 번째 테스트로는 종료 시점인 "END" 가 입력됐을 경우 while문을 종료한다.

두 번째 테스트로는 길이가 다르다면 different를 출력하고 반복문을 continue한다.

위의 두 가지 테스트를 모두 통과하였다면, 이제 한 글자씩 매칭을 해봐야 한다.
회수한 test 문자열을 인덱스를 이용해서 0부터 for문으로 돌았다.
find() 함수를 이용해서 특정 문자의 인덱스 위치를 발견해 index에 저장하였다.

ans = ans[:index] + ans[index+1:]

중요한 것은 이 코드인데, test 문자열의 문자 c와 일치한다면 ans의 문자열 중 문자 c를 삭제하는 코드이다.

처음에는 remove() 함수를 이용해서 지웠었는데, 그렇게 하니 ans 문자열에 있는 모든 c 문자를 삭제해서 1:1 매칭이 제대로 될 수 없었다.

그래서 ansindex에서 1개만 삭제할 수 있도록 슬라이싱을 해주었다.

그렇게 모든 테스트를 통과하면 same을 출력해준다.

profile
프로그래밍 공부 블로그

0개의 댓글