다이나믹 프로그래밍

  • 가지를 만드는 문제로, 기존 내용을 기록해두어서 재 반복 계산 방지
  • 점화식 생성

 

1. 1로 만들기

초기 idea : 시작 값에서 2,3,5순으로 나누어지는 자리에 min 횟수를 입력

 

2. 개미 전사

초기 idea : 창고를 1개이상 건너서 방문 = i-3과 i-2중 큰 값에 현재 값 더하기

정답 : 현재를 버리고, 그전값을 넣는 것이 최대일 수도 잇어서 (i-2)와 (i)값 vs (i-1)중 선택

 

3. 바닥 공사

idea : 타일 크기에 따라서 i-2에는 2칸을 i-1에는 1칸을 붙인다고 생각하고 1칸의 연결은 i-1에 포함이라고 계산

효율 idea : if-else < 초기 값을 최대보다 큰 값으로 설정해서 min() 간편하게

 

4. 효율적인 화폐 구성

초기 idea : 가장 작은 단위부터 해당 화폐로 만들어지는 경우에는 화폐 단위를 모두 더한 곳 처리

효율 idea : 모든 단위가 계산되야하므로 단위 기준 for문에서 전 값에서 현재 화폐 단위를 더할 수 있는지

탐색

  • 순차 탐색 : 앞에서부터 차례로 확인 → 최악 O(N)
  • 이진 탐색 : 반으로 쪼개서 탐색 → o(NlogN) ⇒ 트리 자료구조
def binarySearch(a, target, start, end):
	if end < start: # 엇갈림 : target 없음
		return None
	mid = (start+end)//2
	if a[mid] == target:
		return mid
	if a[mid] < target:
		return binarySearch(a, target, start, mid-1)
	else:
		return binarySearch(a, target, mid+1, end)

a = [1,2,3,4,5,6]
print(binarySearch(a,1,0,len(a)-1)

 

1. 부품 찾기

초기 idea : 각 리스트를 비교해가면서 탐색 범위 줄이기

효율 idea : 탐색 → 이진 탐색 or 계수 정렬로 존재 여부 확인

 

2. 떡볶이 떡 만들기

초기 idea : 중간 길이부터 잘라보면서 이진 탐색으로 위치 선택

  • 높이가 10억보다 작거나 같기 때문에 계수 정렬 불가능

정렬

 

선택 정렬 O(N**2)

  • 가장 작은 값 찾아서 첫번째 자리와 교환 → (n-1)번째 까지
array = [5,4,3,2,1,6,7,8,9]

for i in range(len(array)-1):
    m = i
    for j in range(i+1, len(array)):
        if array[m] > array[j]:
            m = j
    array[i], array[m] = array[m], array[i] # 실제 교환은 최소

 

삽입 정렬

  • 정렬이 되어있으면 최적으로 O(N)이라 정렬이 많이 된 경우에 좋음
  • 첫 원소는 정렬되었다고 가정하고, 두번째 원소부터 어디에 위치할지 비교
a = [5,4,3,2,1,6,7,8,9]

for i in range(1, len(array)):
    for j in range(i-1, 0, -1): # 작으면, 한칸씩 앞으로
        if a[i] > a[j]:
            a[i], a[j] = a[j], a[i]
        else:
            break

 

퀵 정렬

  • 평균 O(NlogN)로 상대적으로 빠름,그러나 최악은 O(N**2)이며 왼쪽 데이터가 정렬되어있는 경우는 매우 느려서 선택 정렬과 반대
  • 첫 원소를 피벗으로 잡아서 왼쪽에서 큰 값과 오른쪽에서 작은 값 위치를 변경 → 왼쪽과 오른쪽의 탐색 값이 서로 엇갈리면, 작은 데이터와 피벗 위치 교환 → 피벗 기준 2개인 각 파트에서 다시 피벗을 설정 후 진행
def quick(a, start, end):
    # 원소가 1개인 경우, 종료
    if start == end:
        break

    p = a[start]
    i = start+1
    j = end
    while i <= j:
        while a[i] < p:
            i += 1
        while a[j] < p:
            j -= 1
        if i > j:
            break
        if i > j: # 엇갈림 -> while문 종료
            a[j], a[start] = a[start], a[j]
        else:
            a[i], a[j] = a[j], a[i]

    a[0], a[i] = a[i], a[0]
    quick(a, start, i-1)
    quick(a, i+1, end)

a = [5,4,3,2,1,6,7,8,9]
quick(a, 0, len(a)-1))
print(a)

# python의 장점 이용
def quick(a):
    # 원소가 1개인 경우, 종료
    if len(a) == 1:
        return a

    p = a[0]
    rest = a[1:]
    left = [x for x in rest if x <= p]
    right = [x for x in rest if x > p]

    return quick(letf) + p + quick(right)

a = [5,4,3,2,1,6,7,8,9]
print(quick(a))

 

계수 정렬 : O(데이터 개수 + 데이터 최댓값)을 최악에도 보장

  • 특정 조건(데이터 범위가 제한되어 정수로 표현 가능한 경우)에서만 사용 가능하지만, 매우 빠름
  • 모든 데이터+1을 담을 수 있는 리스트 생성 후, 데이터 값과 동일한 인덱스의 데이터를 1씩 증가시키는 방식 ⇒ 1이 들어간 곳은 숫자가 존재함
a = [5,4,3,2,1,6,7,8,9]

result = [0]*(max(a)+1) # 포인트 : 최대값 + 1
for i in a:
    result[i] += 1 # 중복 고려

for i in range(len(result)): # i(인덱스)가 값
    for j in range(i):
        print(i, end = " ")
  • reverse() : O(N)
  • sorted() : O(NlogN) 보장

 

문제 풀이

  1. 정렬 라이브러리 이용
  2. 계수 정렬 or 퀵 정렬 이용

1. 위에서 아래로

초기 idea : 내림차순 정렬 → 수의 개수가 500개 이하 정수로 계수 정렬 이용

효율 idea : 라이브러리 이용

2. 성적이 낮은 순서로 학생 출력하기

  • 라이브러리 + lambda 이용

3. 두 배열의 원소 교체

  • 횟수만큼 max, min 탐색 → 차라리 O(NlogN)인 sort 라이브러리가 나음

DFS/BFS

  • stack = [], queue = deque([]), 재귀 함수 중 1개 이용
  • 주어지는 값 : 인접 행렬(2차원 배열 연결 관계) or 인접 리스트
# DFS
## 재귀 함수 이용
1. 가진 x 노드가 방문했다고 표시
2. 방문 기록 없는 연결 노드와 연결된 값들을 돌아가면서 확인
# BFS
## DEQUE를 이용한 구현 & while을 이용
1. deque()에서 popleft()를 통해 가져와 방문 표시
2. 방문 기록 없는 연결된 노드들 모두를 deque에 삽입과 동시 방문 처리

 

1. 음료수 얼려 먹기

  • 0이 표시된 칸이 연결된 곳에서 1개의 아이스크림이 생성됨

초기 idea

  • 0인 곳을 연결된 cycle 단위로 처리하면서 해당 횟수를 누적
  • 0인 위치 파악 → 해당 [y,x] 누적에서 해당 값의 제거 여부 확인
  • cycle 단위 처리 → stack에 값 누적시켜서 BFS를 통해 방문 처리

효율적 idea

  • 0인 위치 파악 → 전체 탐색으로 0인 경우에 연결된 값들 처리
  • cycle 단위 처리 → 해당 값을 기준으로 상하좌우 방문 처리 및 각 값에 대해 재귀 함수로 확인

 

2. 미로 탈출

  • 0인 자리만을 지나는 최단 경로의 이동해야하는 총 칸의 개수

초기 idea

  • 최단 경로 → 최단 경로니까, 가장 적은 횟수를 거친게 먼저 나오도록 BFS
    • DFS ⇒ 가장 먼저 로 도착하는 값이 최단 경로가 아닐 수도 있음
    • 4방향 중 가장 먼저 탐색되는 값을 기준으로 점차 내려가게 되어서
  • BFS에서 같은 레벨 단위로 밟게 되는 횟수가 같으니 해당 레벨 단위로 누적

효율적 idea

  • 레벨 단위 처리가 아닌, 현재 해당 위치를 밝게 되는 최단 경로를 visited 여부 대신 기록

구현

: 떠올린 풀이를 요구사항에 맞게 코드로 옮기기 어려운 문제

  • 완전 탐색(모든 경우 다 계산) + 시뮬레이션(제시한 알고리즘 차례로 수행) → 카카오에서 api 개발 문제(웹 서버 지식 필요)

1-1. 상하좌우

  • N x N 크기에서 왼쪽 위 (1,1)인 경우, LRUD를 받아서 도착 지점 출력
  • 공간 벗어난 움직임 무시

idea

  • 공간 벗어난게 있어서 추적 필요 → 시점 : UD는 y좌표 범위, LR은 x좌표범위

1-2. 시각

  • 주어진 시간 전에 k값이 포함되는 시간의 개수

[Probelm]

  • 문제에 대한 이해 충실도 부족 → 모든 세부 내용 기록
  • 총 계산한 경우의 전체 경우의 수가 맞는지 미확인

idea

  • 실제로 모든 값을 돌아가면서 들어가 있는지 문자열로 바꿔서 확인

2. 왕실 나이트

  • 현재 위치에서 L자 모양이 들어갈 수 있는 좌표 수 계산

idea

  • 갈 수 있는 후보지가 칸을 넘어가지 않는 경우, 누적
  • 이동 문제에서는 중간 값 저장하는 dx, dy 자주 사용

3. 게임 개발

  • 항상 현재 방향 기준에 왼쪽 방향부터 갈 수 있는 육지 칸을 탐색

idea

  • 계산 내용이 복잡한 경우 → 함수로 분리
  • 기존에 가본 칸에 대해서는 0,1외로 2라는 값을 추가해서 갈수 있는 1과 구분

그리디

: 기준에 따라 뒤 상황 고려 없이 현재 상황에서 가장 좋은 것 선택

→기준 잡기 + 다른 해 불가능을 보장

1. 거스름돈 문제 - O(K)

  • K : 화폐의 종류 & N : 거스름 금액
  • 큰 화폐 단위부터(최소 개수) 계산 시, 항상 작은 단위의 배수로 다른 최적 해가 나올 수 없음이 보장

2. Test 큰 수 법칙

  • (x) : 가장 큰 것부터 최대 개수 누적
    • 작은 수를 사용 시, 더할 수 있는 횟수 제한으로 절대 커질 수 없는 것 보장
  • (o) : 최대한 큰 수 이용 = 연속적으로 K 사용 후 2번째 큰 수 더하기
  • [ Code]
    • K+1개가 주기로 더해져 K전에 M이 끝나면, K 더하고 나니 끝나는 경우와 분리
      • Q = M // (k+1)
      • R = M % (k+1) ⇒ 남은 횟수를 통한 개수 지정 가능
    • 나머지
      • 0 : Q*(K*L1 + L2)
      • 1 ~ K : Q*(KL1 + L2) + RL1
      ( M // (K+1) ) * (K*L1 + L2) + ( M % (K+1) ) * L1
    N, M, K = map(int, input.split()) # 배열 길이, 더하는 숫자 개수, 연속 가능 횟수
    num = list(map(int, input.split()) # 배열
    
    num.sort()
    
    L1 = num[-1]
    L2 = num[-2]
    Q = M // (K+1)
    R = M % (K+1) # = M - Q*(K+1) (계산할 숫자의 개수 - 주기성 가진 횟수)
    
    ## solution 1
    L1_rest = R # 주기 외 L1
    loop = L1*K + L2 # 주기의 합
    **print(loop*Q + L1_rest*L1)**
    
    ## solution 2 : **L1이 해당하지 않는 것은 L2에 해당함**
    count = Q*K + R
    **print(count*L1 + (M-count)*L2)**
    

[Question ]

  • 순차 정렬
    • 문제 : slist.sort() : 대신 큰수부터 구하는 정렬 찾기
    • 해결 :
  • 파이썬 복사
    • 문제 : if) L2_cnt = L1_cnt : L1과 같은 곳을 가르키지 않는 상태여야함!
    • 해결 :

3. 숫자 카드 게임

  • 각 행의 가장 작은 수 중 가장 큰 수 & 행이 우선적으로 선택되는 상황에서 해당 행에서 가장 작은 수를 뽑아야하는 조건으로 더 큰 수 불가능 보장
  • [ Code ]
  • N, M = map(int, input().split()) #행, 열 mx = 0 for n in range(N): mn = 1000 # for문 1번마다 숫자 변경 cards = list(int, input.split()) for m in cards : if mn > m : mn = n if mn > mx : mn = mx print(mx)

4. 1이 될 때까지

  • K로 나눠지지 않으면, 나눠질때까지 -1인 작업이 수행됨 + 조건에 의해 k로 나누어 떨어질 때만 2번 수행 가능하기에 보장됨

⇒ (o) 나누기를 빼기보다 우선순위 높게 동작시키기 + N을 크게 줄일 수 있는 방법이 나누기임

  • [ Code ]
    • else가 최대한 없는 것이 좋음 → else에 해당하면 0이 더해지게
    N, K = map(int, input().split()) # 주어진 수, 나누는 수
    count = 0
    while N > 1:
        r = N % K
        if r == 0:
            N /= K
            count += 1
        else :
            N -= r
            count += r
    print(count)
    

1. 사이트 : 백준 & 코드포스

국내

  • 백준
  • 코드업
  • 프로그래머스
  • SW export academy

국외

  • 코드포스 → 민트 이상 목표
  • 탑코더
  • 립코더
  • 코드셰프

2. 조건

  • 제출 횟수 제한
  • 온라인 IDE 공개 여부 확인
  • 접근 방식 설명

3. 복잡도

⇒ 코딩 테스트에서는 최악의 시간 복잡도가 중요하여, 가장 우선으로 고려!

  • 시간 복잡도 : 연산 횟수
  • 공간 복잡도 : 메모리 양 - 128~512MB
    • 1024(2^10)개 단위로 뜀
    • Byte (바이트)< KB(킬로바이트) < MB(메가바이트) < GB(기가바이트) < TB(테라바이트)

1초 예시( 숫자가 크면 log(N) 빠름 < log(N logN)

  • N = 500, O(N^3)
  • N = 2000, O(N^2)
  • N = 100,000 십만, O(N logN)
  • N = 1,000,000 백만, O(N)

4. 최신 출제 경향

  • 정렬
  • 그리디
  • 구현
  • DFS/BFS
  • (다이나믹 프로그래밍, 그래프 이론)

5. 질의응답

  1. 알고리즘 풀이
    • 어떤 상황에서 어떤 알고리즘이 사용되는지 & 비교해서 왜
    • 정렬 알고리즘의 시간 복잡도
      • 선택, 삽입은 최악의 O(N^2)의 시간 소요, 그러나 병합 정렬 같이 최악 경우에도 O(N logN) 보장추가로, 계수 정렬 같은 O(N+K)를 보장하는 정렬 알고리즘 사용 가능
      • 대부분 라이브러리에서도 O(N logN) 형태로 정렬 기능 제공
  2. 포트폴리오 징의응답
    • 개발 프로젝트에 필요한 지식 요구(신입 교육 불가 : 잠재력 < 경험)
    • 토이 프로젝트 정리
    • 웹 개발자 → spring
    • 클라우드 이용 경험
  3. 공학 지식 질의응답
    • 서버 개발 : 멀티 스레딩, 메모리 관리
    • 웹 개발 : GET, POST 차이 / tcp, udp, http, https 개념과 원리
    • 데이터베이스 : 정규화, 인텍스, NoSQL

파이썬의 자료구조

1. 딕셔너리, 맵, 해시 테이블

  • 주어진 키와 연관된 객체가 효율적으로 처리됨

dict(), {} -> 시간복잡도 O(1)

2. 배열 데이터 구조

list : 가변적 동적 변형
tuple : 객체 변경 불가능

3. 자료구조 사용

stack : list에서 append : O(1), pop :O(n) 이용 < collections.deque 이용
queue : collections.deque 이용(이중 연결리스트로 구현)하면 어느쪽에서든지 O(1)
heap : O(logn) heapq의 heappush(q, 값) 또는 heappop(q) 이용


객체 복사

  • 깊은 복사
  • 얉은 복사 : 새 객체 생성 후 원본 객체의 자식에 대한 참조로 채움
xs = [[1],[2]]

# 얕은 복사 : xs에 값이 추가된다고 변하지는 않으나, ys 자식 객체가 수정시에는 같이 변동
ys = list(xs)

# 깊은 복사 : 원본과 독립적
import copy
zs = copy.deepcopy(xs)

'알고리즘' 카테고리의 다른 글

[프로그래머스] 투포인터  (1) 2023.11.27
[프로그래머스] 백트래킹  (0) 2023.11.22
[프로그래머스] 이진 탐색  (1) 2023.11.20
Back-tracking (N-Queens 등)  (0) 2023.09.06
[프로그래머스] 무지의 먹방 라이브  (0) 2023.06.30

🌟변화

해당 문제를 푸는 방법을 코딩으로 표현한다고만 생각하던 전과 달리

기본적인 알고리즘 문제를 풀 때, 받아들이는 정보의 처리(input)나 직접 예시를 작성해보고

실제로 어떤 규칙을 적용해서 계산하고 있는지 계산 방식을 떠올리는 과정을 통해서

조금 더 정답에 가까운 방법에 접근할 수 있게 되었습니다.

📕 학습

또한, 각 유형마다 기본적으로 문제를 푸는 방식이 존재한다는 사실을 알게 되었습니다.

수학적 방법을 이용하는 문제가 있는가 하면, 자료구조를 이용해야 시간내에 풀 수 있는 방법도 존재하며

각 문제를 유형에 따라 구분 후 어떤 방식을 이용할지 선정하고, 그제야 예시를 통해 규칙을 적용하는 방식을 통해 문제 풀이를 진행하고 있습니다.

🔥 앞으로

비슷한 풀이까지는 가지만, 아직은 완벽한 결과를 혼자의 힘으로 내기는 어려워서

앞으로도 꾸준히 해서 현재 silver인 백준 레벨과 저의 실력을 키워보려고 합니다.

'알고리즘 > BACKJOON' 카테고리의 다른 글

[BACKJOON] Dynamic programming  (0) 2023.03.06
[BACKJOON] len() & 원형 queue  (0) 2023.03.06
[BACKJOON] stack 문제  (0) 2023.03.06
[BACKJOON] 백준 자료구조  (0) 2023.03.06
[BACKJOON] 백준 공부 restart  (0) 2023.03.06

Dynamic programming

동적 프로그래밍이란,

잡한 문제를 간단한 여러 개의 문제로 나누어 푸는 방법을 말합니다. 여러 개의 하위 문제를 해결하는 과정을 결합하여 최종 목적에 도달하게 됩니다.

문제 해결을 위한 모든 방법을 검토 후 최적의 풀이법을 찾아내는 방법으로
계산 횟수를 줄일 수 있어서 최단 경로 등 최적화에 사용됩니다.

 

비교 : 그리디 알고리즘

그리디 알고리즘은 동적 프로그래밍의 모든 방법을 고려해야 한다는 단점을 극복했습니다. 항상 최적해를 구하지는 않지만** 최소 신장 트리** 같은 여러 문제에서 최적해를 구할 수 있다는 것이 입증되었습니다.

 

가능한 빠른 이동 방식 탐색

  • 동적 프로그래밍 : a -> b까지 이동의 모든 정보를 통해 최적 경로를 탐색
  • 그리디 알고리즘 : 교차로가 보일 때마다 가장 빠른 경로 검색

=> 시간이 걸리는 최적의 값 vs 시간에 비해 효율적인 값


아래 문제는 다이나믹 프로그래밍, 줄여서 DP 유형의 문제로 해당 문제를 푸는 방식으로는 크게 3가지가 있습니다.

백준 - 1463번 1로 만들기 문제(https://www.acmicpc.net/problem/1463)

 

Bottom up

import sys
n = int(sys.stdin.readline())
d = [0]*(n+1)

for i in range(2,n+1):
    d[i] = d[i-1] + 1
    if i%2 == 0:
        d[i] = min(d[i], d[i//2]+1)
    if i%3 == 0:
        d[i] = min(d[i], d[i//3]+1)

print(n)

위 방식은 목표인 1이 되는 방법이 아닌, 1에서 n으로 올라가면서 그 값이 될 수 있는 최소 값을 누적된 값을 이용해서 해결합니다.


해당 문제에서 우리는 2와 3 모두로 나누어 떨어지는 경우, 2 또는 3 중 하나로 나누어 떨어지는 경우에서 최소 횟수가 이용될 수 있게 하였습니다.

 

Top down

import sys
n = int(sys.stdin.readline())
d = {1:0}

def make_one(n):
    if n in d:
        return d[n]
    if i%2 == 0 and i%3 == 0:
        d[n] = min(rec(n//3)+1, rec(n//2)+1)
    elif i%2 == 0:
        d[n] = min(rec(n-1)+1, rec(n//2)+1)
    elif i%3 == 0:
        d[n] = min(rec(n-1)+1, rec(n//3)+1)
    else:
        d[n] = rec(n-1)+1
    return d[n]
print(n)

위와 달리 n에서 될 수 있는 하위 값을 찾아서 내려가는 방식으로 이용하였습니다. 재귀함수로 하위 값이 dict()에 존재할 수 있도록 조정하여 최소 횟수가 dict()에는 누적되고, 해당 값을 기준으로 목표 값의 횟수를 탐색할 수 있게 됩니다.

 

deque

import sys
from collections import deque
n = int(sys.stdin.readline())
q = deque([n])
visited = [0]*(n+1)

while q:
    value = q.popleft()
    if value == 1:
        break
    if value%3 == 0 and visited[value//3] == 0:
        q.append(value//3)
        visited[value//3] = visited[value]+1
    if value%2 == 0 and visited[value//2] == 0:
        q.append(value//2)
        visited[value//2] = visited[value]+1
    if visited[value-1] == 0:
        q.append(value-1)
        visited[value-1] = visited[value]+1
print(visited[1])

최소 계산 횟수를 구하는 문제이기 때문에 항상 최단 거리를 보장하는 BFS방식 또한 적용할 수 있습니다. BFS에서는 방문한 노드를 visited = []에 기록해두고, 다음 방문할 노드를 deque에 넣게 됩니다. 방문하지 않은 노드를 찾아가야하기에 모든 다음 단계를 q에 넣기전에는 해당 노드의 방문 여부를 확인합니다.

 

해당 문제에서는 visited에 방문 횟수를 기록하는 방식을 이용하여, 현재 방문 횟수+1을 q에 넣는 다음 방문 노드의 visited에 넣어서 횟수를 누적해서 넣어둡니다. 그리고 1이 처리되는 경우에는 바로 중지 후 담긴 횟수를 출력하게 하여 가장 최소 횟수가 출력될 수 있습니다.


참고 자료

 

[백준] 1463 1로 만들기 (DP) - Python / 자세한 설명 / 여러가지 풀이 / 실버1

Contents 1. 문제 정의 및 예제 해석 (문제 링크: https://www.acmicpc.net/problem/1463) 1463번: 1로 만들기 첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다. www.acmicpc.net 주어진 수 x에 대해

bio-info.tistory.com

 


원본 : [BACKJOON] Dynamic programming

'알고리즘 > BACKJOON' 카테고리의 다른 글

[BACKJOON] 기초 알고리즘 1/2 후기  (0) 2023.03.08
[BACKJOON] len() & 원형 queue  (0) 2023.03.06
[BACKJOON] stack 문제  (0) 2023.03.06
[BACKJOON] 백준 자료구조  (0) 2023.03.06
[BACKJOON] 백준 공부 restart  (0) 2023.03.06

+ Recent posts