2407번: 조합

n과 m이 주어진다. (5 ≤ n ≤ 100, 5 ≤ m ≤ 100, m ≤ n)

www.acmicpc.net

 

매우 간단한 문제긴 한데 놓쳤던 부분이 있어서 시간 소요가 꽤 있었다. 기억을 위해 기록한다!

 

조합 공식

조합 공식은 위 그림과 같다. 조합은 서로 다른 n개중에 r개를 선택하는 경우의 수를 의미한다. (순서 상관 없음)

 

팩토리얼을 구현하는 방법이 여러가지가 있으나 재귀함수 구현에 좀 시간이 걸렸다. 부끄럽다... 일단 재귀함수 ... 후 ... return문이 있어야 함수를 빠져나간다... 재귀 억지로라도 계속 손에 익히자 ㅠㅠ! 재귀로 팩토리얼을 구해도 되고 반복문으로 해도 된다.

 

그러고 나서 나눗셈을 해줘야 하는데 파이썬에서 '/' 과 '//' 연산자 차이를 이제야 다시 알았다.

'/' : 나눗셈 결과를 float형으로 반환

'//' : 나눗셈 결과를 int형으로 반환

 

그래서 /로 연산해준뒤 형변환하면 틀렸다고 나온다. 뭐 뒤의 자리를 까고 그러다가 그랬겠지..? 어차피 저 결과는 무조건 정수가 나올 수 밖에 없으니까 int형으로 리턴하는 '//' 연산자를 쓰는게 맞겠다.

 

코드는 다음과 같다.

n, m = map(int, input().split())
def recur_f(n):
    if n == 1:
        return 1
    else:
        return n * recur_f(n - 1)

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

print(factorial(n) // ((factorial(m) * factorial(n - m))))

 

 

 

15650번: N과 M (2)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해

www.acmicpc.net

 

백트래킹 복습 겸 같은 문제 다른 유형을 풀어봤다.

사실 로직 자체는 그냥 N과 M 문제와 똑같다. 다만 여기에서 이제 수열이 오름차순 정렬이 되어서 출력이 되어야 한다는 조건만 하나 붙어 있을 뿐! 그래서 sorted() 함수를 사용해서 정렬해준 뒤 그 결과가 answer에 없으면 넣어주는 코드를 추가했다.

 

아 그리고 숫자로 이루어진 리스트의 경우 바로 join해주면 int형이라 에러가 난다. 각각의 요소를 str으로 바꿔주는 map함수 잊지말고 적용해주자~!

 

코드는 다음과 같다.

n, m = map(int, input().split())


result = []

answer = []


def back():
    if len(result) == m:
        tmp = sorted(result)
        if tmp not in answer:
            answer.append(tmp)
        return

    for i in range(1, n + 1):
        if i not in result:
            result.append(i)
            back()
            result.pop()


back()

for i in answer:
    print(" ".join(map(str, i)))

n과 m.. (3)은 약간 두렵다 ㅎ

 

6588번: 골드바흐의 추측

각 테스트 케이스에 대해서, n = a + b 형태로 출력한다. 이때, a와 b는 홀수 소수이다. 숫자와 연산자는 공백 하나로 구분되어져 있다. 만약, n을 만들 수 있는 방법이 여러 가지라면, b-a가 가장 큰

www.acmicpc.net

 

소수 판별 알고리즘 중 에라토스테네스의 체 방식을 알고 있어야 시간 초과가 안 나는 문제다. 물론 나는 에라토스테네스의 체 방식을 매번 입력받을 때마다 하고 반복문도 많이 써서 시간초과만 우다다 받았다 ^^ 

 

시간초과를 막는 중요한 포인트가 2개였다...

  1. 에라토스테네스의 체를 범위 내의 모든 숫자에 한해서 1번만 돌리기
  2. 최대한 반복문 줄이기

 

밉다 미워 시간초과

 

최대 입력 가능한 숫자 범위가 1000000이니까 그 안을 일단 다 소수를 판별해버린다! 가능한 소수를 미리 다 계산한 뒤 또 범위 내에서 새로운 리스트를 만들지 말고, 반복문 안에서 True, False값을 체크해서 보기만 해야 시간 초과가 안 난다.

 

시간초과가 이렇게 머리를 아프게 하는지 간만에 느껴본 문제...^^ 같은 골드바흐 문제는 꽤 금방 시간초과를 잡아서 기가 살아있었는데 1시간 내내 계속 시간 줄이다 보니까 기가 쪼그라들었다... 그래도 풀긴 푼거니까... 어디서 시간초과가 나는지 잘 살펴보는 능력도 정말 중요하구나, 또 느낀다.

 

파이썬 코드는 다음과 같다.

erato = [True] * 1000001
erato[0], erato[1] = False, False
for i in range(2, 1001):
    if erato[i]:
        for j in range(i + i, 1000001, i):
            erato[j] = False

while True:
    n = int(input())
    wrongFlag = True
    if n == 0:
        break

    for i in range(3, n):
        if erato[i] == True:
            if erato[n - i] == True:
                wrongFlag = False
                print(n, "=", i, "+", n - i)
                break
    if wrongFlag:
        print("Goldbach's conjecture is wrong.")

 


 

눈물의 코드... (쓸 필요도 없었고 시간초과만 유발했던 녀석들이다)

# n 미만의 수 중 홀수인 소수 리스트 리턴
def getPrimeNums(n):
    return [i for i in range(n) if erato[i] == True and i % 2 != 0]


def getGoldB(target, nums):
    middleIdx = -1
    for i in range(len(nums)):
        if nums[i] > int(target / 2):
            break
        middleIdx = i

    for i in range(middleIdx + 1):
        for j in range(len(nums) - 1, middleIdx, -1):
            if nums[i] + nums[j] < target:
                break
            if nums[i] + nums[j] == target:
                return [nums[i], nums[j]]
    return False

 

나름 시간 좀 줄여보겠다고 다른 골드바흐 문제에서 시간초과 잡아줬던, 중간값 구해서 어찌저찌 해봤는데 역시나 시간초과였다. 그냥 T/F 값 판단해서 바로바로 계산하는게 제일 빠르구만

https://www.acmicpc.net/problem/2609

 

2609번: 최대공약수와 최소공배수

첫째 줄에는 입력으로 주어진 두 수의 최대공약수를, 둘째 줄에는 입력으로 주어진 두 수의 최소 공배수를 출력한다.

www.acmicpc.net

 

이번 기회에 파이썬에서 사용 가능한 최대공약수(gcd), 최소공배수(lcm) 구하는 방법을 다 정리해보았다.

 

가장 베이직한 방법

최대공약수 : 숫자 x,y 중 더 작은 숫자부터 1까지 -1 해가며 x%i == 0 and y%i == 0인 숫자 찾기

최소공배수 : 숫자 x,y 중 더 큰 숫자부터 (x*y)까지 +1 해가며 i%x == 0 and i%y == 0인 숫자 찾기

 

유클리드 호제법

x,y의 gcd는 y와 r의 gcd와 같다. ( x%y = r)

 

즉 계속해서 x의 자리에 y값을 대입하고, y에는 x%y 값인 r을 대입하면 x%y == 0 인 순간의 y값이 바로 gcd이다.

 

최소공배수는 x*y를 위에서 구한 gcd로 나눠주면 된다.

 

라이브러리 활용

이게 사실 제일 편하다..^^ 파이썬의 math 라이브러리는 인자를 심지어 여러개 받을 수 있는 최대공약수, 최소공배수를 구해주는 함수를 제공한다.

math 라이브러리를 import 해주고 math.gcd( 인자, 인자, ... ) math.lcm( 인자, 인자 ,... ) 해주면 된다.

 

 

코드는 다음과 같다.

n, m = map(int, input().split())

# 가장 베이직한 방법
def getGCD_basic(x, y):
    for i in range(min(x, y), 0, -1):
        if x % i == 0 and y % i == 0:
            return i


def getLCM_basic(x, y):
    for i in range(max(x, y), (x * y) + 1):
        if i % x == 0 and i % y == 0:
            return i


# 유클리드 호제법
def getGCD_euclid(x, y):
    while y:
        x, y = y, x % y
    return x


def getLCM_euclid(x, y):
    return (x * y) // getGCD_euclid(x, y)


# 파이썬 라이브러리 활용
import math

print(math.gcd(n, m))
print(math.lcm(n, m))

 

유클리드 호제법은 정말... 정말 오랜만에 다시 들여다봤다...^^ 수학을 열심히 했던 학생이었는데 이렇게까지 싸그리 까먹을 수 있다니 슬프다. 안 좋은 일이나 빨리 까먹지 이런 수학적 지식만 홀라당 다 까먹어 버렸다.

10.7부터 week1을 달리고 있다.

https://www.acmicpc.net/workbook/view/8997

 

문제집: Week 1 : 수학 (postcookie)

 

www.acmicpc.net

 

백준 15649 N과 M (1)

N과 M문제는 자다가도 풀 수 있어야 한다고 해서 순열로 풀었다가 눈물 좍좍 흘리면서 백트래킹을 뿌셨다.

 

백트래킹은 불필요한 경우를 탐색하지 않는다. 즉 특정 조건에서는 탐색을 그만하고 뒤로 되돌아가는(back) 성질을 가지고 있다. 따라서 DFS로 무작정 들이박으면 시간초과가 나는 상황에 유용하게 쓰이겠다.

 

숫자 1~N까지 중복 없는 M개의 요소를 가진 수열을 구하는 문제이다.

따라서 1) 수열의 길이가 M을 넘으면 안됨 2) 배열 내에 중복 X 두 가지 조건을 만족하는 숫자 뽑기를 진행하면 된다.

 

ex. N = 4, M = 3

  1. 첫번째 숫자로 1,2,3,4 중 가장 작은 숫자인 1을 선택한다. -> for 문으로 순차 증가
  2. 두번째 숫자로 2,3,4 중 가장 작은 숫자인 2를 선택한다. -> 선택 과정에서 배열 안에 존재하는지 체크하여 중복 제거
  3. 세번째 숫자로 3,4 중 가장 작은 숫자인 3을 선택한다.
  4. 수열의 길이가 M(3)이 되었으므로 다시 단계 3로 돌아간다. -> 재귀 탈출
  5. 세번째 숫자로 4를 고른다. 
  6. 수열의 길이가 M(3)이 되었으므로 단계 2로 돌아간다.
  7. 두번째 숫자로 다음 작은 숫자인 3를 선택한다.
  8. 세번째 숫자로 2,3,4 중 2를 선택한다. -> 추후 여기에서 다시 숫자를 고를 때 3은 이미 배열안에 존재하므로 X
  9. ... 반복

즉 위의 과정을 코드로 구현하면 다음과 같다. 

n, m = map(int, input().split())
result = []

def backTracking():
    # 수열의 길이가 m이면 출력해주고 전단계로 돌아가기 (재귀 탈출)
    if len(result) == m:
        print(" ".join(map(str, result)))  # "구분자".join(리스트)
        return
    # 1 ~ n 까지 체크
    for i in range(1, n + 1):
        # 중복 체크
        if i not in result:
            result.append(i)
            backTracking()
            # return 문으로 돌아온 경우 실행되는 부분
            result.pop()  # n=4, m=3일때 1,2,3이 들어온 경우 3을 없앰으로써 전단계로 돌아감


backTracking()

 

백트래킹 어렵다. 재귀라서 더 어렵게 느끼는 것 같기도 하고...! 익숙해질때까지 킵고잉이다 정말

 

 


출력 관련해서 문자열 다룰 때 자주 쓰이는 함수 join도 짚고 가보자.

 

"구분자".join(리스트) 형태로 사용하며 join 함수는 매개변수로 들어온 리스트에 있는 요소 하나 하나를 합쳐서 하나의 문자열로 바꿔 반환하는 함수이다. "구분자"를 값과 값 사이에 넣어서 하나의 문자열로 합쳐준다!

 

파이썬이 문자열 다룰 때 깡패인 언어인 만큼 join함수에도 익숙해지자. 자주 써야된다~!

백준 기본 입출력 예제

백준 : 2557, 1000, 2558, 10950, 10951, 10952, 10953, 11021, 11022, 11718, 11719, 11720, 11721, 2741, 2742, 2739, 1924, 8393, 10818, 2438, 2439, 2440, 2441, 2442, 2445, 2522, 2446, 10991, 10992)

 

백준 10951

문제를 풀다보니 기억할 문법이 있어 적는다. 몇 개의 테스트케이스가 주어지는지 모르기 때문에 숫자 외의 다른 것이 입력되거나 수가 입력되지 않아 에러가 발생하면 그를 처리해줘야 한다. -> try - except 구문

while True:
    try:
        a, b= map(int, input().split())
    except:
        break
    print(a+b)

 

백준 별찍기 시리즈

하다보니 1시간이나 지났다 그래.. 1~2학년때 별찍기 한문제에 1시간 걸렸던거 생각하면,,^^

# 2445
n = int(input())

for i in range(1, n + 1):
    print("*" * i + " " * 2 * (n - i) + "*" * i)

for i in range(n - 1, 0, -1):
    print("*" * i + " " * 2 * (n - i) + "*" * i)

# 2522
n = int(input())

for i in range(1, n + 1):
    print(" " * (n - i) + "*" * i)
for i in range(n - 1, 0, -1):
    print(" " * (n - i) + "*" * i)

# 2446
n = int(input())

for i in range(1, n + 1):
    print(" " * (i - 1) + "*" * (1 + 2 * (n - i)))

for i in range(n - 1, 0, -1):
    print(" " * (i - 1) + "*" * (1 + 2 * (n - i)))

# 10991
n = int(input())

for i in range(1, n + 1):
    print(" " * (n - i) + "*" + " *" * (i - 1))

# 10992
n = int(input())

for i in range(1, n + 1):
    if i == 1:
        print(" " * (n - i) + "*")
    elif i == n:
        print("*" * (1 + 2 * (n - 1)))
    else:
        print(" " * (n - i) + "*" + " " * (1 + 2 * (i - 2)) + "*")

 

추가로 week 1 의 이 문제들도 풀었다.

(21.12.3 완료) 1. 코드업 100제

(22.10.7 완료) 2. 입출력 기본 문제들 (https://plzrun.tistory.com/entry/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C%ED%92%80%EC%9D%B4PS-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0 참고)

백준 : 2557, 1000, 2558, 10950, 10951, 10952, 10953, 11021, 11022, 11718, 11719, 11720, 11721, 2741, 2742, 2739, 1924, 8393, 10818, 2438, 2439, 2440, 2441, 2442, 2445, 2522, 2446, 10991, 10992

 

여기부터는 이 블로그 글을 참고해서 진행하려고 한다!

https://dev-dain.tistory.com/155

 

초급-중급 알고리즘 스터디 커리큘럼 추천 (3개월)

막학기에 알고리즘 스터디를 하고 싶어서 스터디를 구했..는데 이번에도 내가 팀장을 맡아서 직접 커리큘럼을 짜게 됐다. 스터디를 처음 하시는 분들도 계셔서 초급 수준으로 스터디를 정했고,

dev-dain.tistory.com

 

수학 -> 자료구조 -> 재귀 -> DP -> 그래프(DFS/BFS) -> 최단경로 -> 이분탐색 -> 분할정복 -> 그리디 -> 완전탐색, 시뮬레이션, 구현(가능하면 이걸 많이 푸는 게 좋다. 구현력이 늘고 풀 수 있는 문제가 많아진다) -> 문자열 -> 투 포인터, 슬라이딩 윈도우 -> 백트래킹 1주씩

 

3. 수학 (합공식, 피보나치수, 약수, 최대공약수, 최소공배수, 소수, 조합과 순열)

https://www.acmicpc.net/workbook/view/8997

 

문제집: Week 1 : 수학 (postcookie)

 

www.acmicpc.net

 

22.10.7 

입출력 + week1 4문제

22.10.8

빡세게 8문제!

22.10.9

https://school.programmers.co.kr/learn/courses/30/lessons/92342

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

잘못된 접근

DP로 접근했는데 애초에 재귀를 보면서 분명 같은 경우를 살펴볼 일이 없어야 하는데... 같은 경우를 체크한다...ㅠㅠㅠ 

실패한 소스코드

import copy

frodo = [0 for _ in range(11)]
answer = []
max_gap = 0

def cmpScore(frodo, apeach):
    score_f = 0
    score_ap = 0
    for i in range(11):
        if frodo[i] > apeach[i]:
            score_f += 10-i
        elif frodo[i] <= apeach[i] and apeach[i] != 0:
            score_ap += 10-i
        else:
            continue
    return score_f - score_ap

def isFrodoWinner(frodo, apeach):
    score_f = 0
    score_ap = 0
    for i in range(11):
        if frodo[i] > apeach[i]:
            score_f += 10-i
        elif frodo[i] <= apeach[i] and apeach[i] != 0:
            score_ap += 10-i
        # k점에 아무도 못 맞힌 경우
        else:
            continue
    if score_f > score_ap:
        return True
    else:
        return False
    
def shoot(score, n, info):
    global max_gap
    # 해당 점수 맞히는 경우
    frodo[10-score] = info[10-score] + 1
    if sum(frodo) == n:
        # print(frodo, isFrodoWinner(frodo, info))
        if isFrodoWinner(frodo, info):
            if len(answer) == 0:
                answer.append(copy.deepcopy(frodo))
                max_gap = cmpScore(frodo, info)
            else:
                if max_gap < cmpScore(frodo, info):
                    answer.clear()
                    answer.append(copy.deepcopy(frodo))
                    max_gap = cmpScore(frodo, info)
                elif max_gap == cmpScore(frodo, info):
                    answer.append(copy.deepcopy(frodo))
            return
    
    elif sum(frodo) > n:
        frodo[10-score] = 0
        if score - 1 < 0:
            return
        else:
            shoot(score-1, n, info)
    else:
        if score - 1 < 0:
            return
        else:
            shoot(score-1, n, info)
    
    # 해당 점수 안맞히고 넘어가는 경우
    frodo[10-score] = 0
    if score - 1 < 0:
        return
    else:
        shoot(score-1, n, info)

def solution(n, info):
    shoot(10, n, info)
    
    if len(answer) == 0:
        answer.append([-1])
    else:
        answer.sort(key=lambda x:(-x[10], -x[9], -x[8], -x[7], -x[6], -x[5], -x[4], -x[3], -x[2], -x[1], -x[0]))            
    print(answer)
    return answer[0]

 


올바른 접근

완전 탐색으로 접근

0점부터 10점까지 모든 점수를 획득할지 말지만 결정하면 되므로 모든 경우의 수는 2^11 로 충분히 살펴볼 수 있다. 2시간 넘게 DP로 끙끙 앓았더니 슬프다. 운동하고 와서 다른 사람 코드 좀 보고... 카카오 코테 보기 전날 한번 더 완탐 문제들 풀어볼 때 다시 봐야겠다. 아직 문제 유형을 파악하는데 서툴구만 흑

 

BFS 접근

1. 10점부터 0점까지 화살 쏨

현재 몇점인지 : score ( 0 -> 10점과녁)

현재 라이언 과녁 상황 : lion [0 for _ in range(11)]

 

2. 어피치를 이기려면

 1) 어피치보다 +1발

 2) 해당 점수 포기

* 주의할 점은 그냥 '=' 연산자 써서는 X. copy() 메소드로 깊은 복사 해주자.

( 2차원 이상의 배열을 깊은 복사 해주려면 copy 라이브러리의 deepcopy() 메소드 써야한다. 리스트의 copy() 메소드는 1차원까지만 깊은 복사 지원 )

 

3. 조건 체크

 1) 화살을 n발보다 더 쏜 경우 ( sum(lion) > n ) : 패스

 2) 0점까지 갔는데 다 못 쏜 경우 ( score == 10 ): 마지막 과녁에 다 쏘기

 3) 다 쏜 경우 ( sum(lion) == n ) : 점수 계산. 최대 점수차 갱신해가면서 최대 점수차를 내는 경우만 따로 배열로 저장하기

 

4. shoot() 함수를 실행한뒤 최종 조건 체크

 1) 리턴값이 빈 배열인 경우 : [-1] 리턴

 2) 리턴값 배열의 길이가 1인 경우 : 해당 배열 리턴

 3) 그 외의 경우 : 가장 낮은 점수를 많이 맞힌 경우 리턴.

  * score을 통해 가장 높은 점수부터 쏘기 시작했으므로 배열의 가장 마지막 요소가 해당됨.

 

python 코드

from collections import deque


def shoot(n, info):
    q = deque([(0, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])])
    result = []  # 가능한 시나리오 모두 저장
    max_gap = 0  # 최대 점수차

    while q:
        score, lion = q.popleft()

        # 화살 다 쐈으니 점수 계산
        if sum(lion) == n:
            score_ap, score_l = 0, 0
            for i in range(11):
                if not (info[i] == 0 and lion[i] == 0):
                    if info[i] >= lion[i]:
                        score_ap += 10 - i
                    else:
                        score_l += 10 - i

            if score_ap < score_l:
                gap = score_l - score_ap
                if max_gap > gap:
                    continue
                if max_gap < gap:
                    max_gap = gap
                    result.clear()
                result.append(lion)
        # 화살을 n발보다 더 쏜 경우
        elif sum(lion) > n:
            continue

        # 마지막 과녁까지 간 경우, 화살 마지막 과녁에 다 쏘기
        elif score == 10:
            tmp = lion.copy()
            tmp[score] = n - sum(tmp)
            q.append((-1, tmp))

        # 슛슛슛
        else:
            # 어피치보다 1발 더
            shoot_lion = lion.copy()
            shoot_lion[score] = info[score] + 1
            q.append((score + 1, shoot_lion))
            # 해당 점수 포기, 패스
            giveup_lion = lion.copy()
            giveup_lion[score] = 0
            q.append((score + 1, giveup_lion))

    return result


def solution(n, info):
    answer = []

    all_cases = shoot(n, info)

    if len(all_cases) == 0:
        answer.append(-1)
    elif len(all_cases) == 1:
        answer = all_cases[0]
    else:
        answer = all_cases[-1]
    return answer

 

 

언제쯤 익숙해지냐 완전탐색.. BFS... DFS...  끙

+ Recent posts