🧐  Prefix   Sum 

Prefix Sum, 구간합, 누적합, 부분합이라 불리며 해당 구간에 대한 합을 빠르게 구하기 위한 알고리즘은 매우 간단하다.

원리는 아래와 같다.
1. 합 배열 S를 만드는 공식
S[i] = S[i-1] + a[i]

2. i~j까지의 구간합을 구하는 공식
S[j] - S[i-1]


이를 이용하면 다음과 같이 일정 구간에서의 합을 빠르게 (시간복잡도를 줄이면서) 구할 수 있다.

  S[5] = A[0] + A[1] + A[2] + A[3] + A[4] + A[5]
  S[1] = A[0] + A[1]
--------------------------------------------------
∴ S[5] - S[1] = A[2] + A[3] + A[4] + A[5]

 

 

🧐 백준 11659 (구간 합 구하기)    Silver III 




🤫  solution_11659

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

prefix_sum = [0]
p_sum = 0
for i in num:
    p_sum += i
    prefix_sum.append(p_sum)


for _ in range(m):
    i, j = map(int, input().split())
    print(prefix_sum[j] - prefix_sum[i-1])

 

 

 

🧐 백준 11660(구간 합 구하기 _ 이차원 배열)    Silver I 



🤫  Algorithm 

 


🤫  solution_11660

n, m = map(int, input().split())
num = [list(map(int, input().split())) for i in range(n)]

S = [[0 for i in range(n + 1)] for i in range(n + 1)]

for i in range(n):
    for j in range(n):
        S[i + 1][j + 1] = (S[i][j + 1] + S[i + 1][j] - S[i][j]) + num[i][j]

for i in range(m):
    x1, y1, x2, y2 = map(int, input().split())
    print(S[x2][y2] - (S[x2][y1-1] + S[x1-1][y2]) + S[x1-1][y1-1])

 

 

 

🧐 백준 10986(구간 합 구하기)    Gold III 


부분 합 개념을 이용해서 풀 때, 부분 합을 계산한 배열을 i, j로 순차적으로 접근할 경우 
O(n^2)으로 시간 초과가 발생하므로 다른 풀이 방법이 필요하다.

🤫  Algorithm 

미리 구해둔 prefix sum에서의 부분합은 S[j] - S[i - 1]의 과정이다.

이때, S[j]와 S[i-1]이 각각 m으로 나눴을 때의 나머지가 동일하다면?
S[j] - S[i - 1] 연산 후 나머지는 0이 되기에 m으로 나눠 떨어진다.

결국 m으로 나눠떨어지는 부분합을 구하는 것은 
나머지가 동일한 idx 중 임의로 nC2를 선택하는 것과 같으므로 다음 알고리즘을 거친다.

[1] [2] [3] [1] [2] -> [0] [1] [3] [6] [7] [9] -> [0] [1] [0] [0] [1] [0] 
-> [0 : 4, 1 : 2] -> 6 + 1 -> 7

 


🤫  solution_10986

import sys
input = sys.stdin.readline

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

rest_list = [0 for _ in range(m)]
rest_list[0] = 1

prefix_sum = 0
for i in range(n):
    prefix_sum += num_list[i]
    rest = prefix_sum % m
    # 나머지 값에 따라서 idx 정보 저장
    rest_list[rest] += 1

cnt = 0
for i in rest_list:
    cnt += i*(i - 1) // 2

print(cnt)

 

 

 

🧐 투 포인터 (two - pointer)

2개의 포인터를 이용해 알고리즘의 시간복잡도를 최적화하는 방법으로 간단한 알고리즘을 사용한다.

주로 n의 최댓값이 크게 잡혀있고 O(nlog n)의 시간복잡도로 해결이 힘들 때, O(n)의 시간복잡도로 구현해야 한다.
이때, 자주 사용되는 것이 바로 투 포인터 알고리즘이다.

투 포인터 알고리즘은 시작 인덱스와 종료 인덱스를 지정해 서로 조건에 따라 접근하도록 하는 것이다.

 

 

🧐 슬라이딩 윈도우

슬라이딩 윈도우 알고리즘은 2개의 포인터로 범위를 지정한 후
범위(window)를 유지한 채로 이동(sliding)하며 문제를 해결하는 알고리즘이다.

투포인터 알고리즘과 매우 비슷하다.

 

 

 

🧐 백준 17609 (회문)    Gold V 




🤫  solution_17609(시간초과)

string_list = [input() for _ in range(int(input()))]

def palindrome(string):
    # 회문인 경우
    if string == string[::-1]:
        print(0)
    # 회문이 아닌경우
    else:
        ans = 2
        for i in range(len(string)):
            new_string = string[:i] + string[i+1:]
            if new_string == new_string[::-1]:
                ans = 1
        print(ans)

for i in string_list:
    palindrome(i)

 

🤫  solution_17609

# 유사 회문 판단 함수
def palindrome_like(string, left, right):
    while left < right:
        if string[left] == string[right]:
            left += 1
            right -= 1
        else:
            return False
    return True
# 회문 함수
def palindrome(string, left, right):
    while left < right:
        if string[left] == string[right]:
            left += 1
            right -= 1
        else:
            pseudo1 = palindrome_like(string, left+1, right)
            pseudo2 = palindrome_like(string, left, right-1)

            if pseudo1 or pseudo2:
                return 1
            else:
                return 2
    return 0

for string in list([input() for _ in range(int(input()))]):
    print(palindrome(string, 0, len(string) - 1))

 

 

 

🧐 백준 1253 (Good Number)    Gold IV 

  


🤫  Algorithm 

# 해당 원소를 제외한 리스트에서 투 포인터(Two-Pointer)를 통해 
# 두 원소의 합이 선택한 원소와 같은지 비교하는 방식으로 해결할 수 있다.

1. num 리스트를 정렬한다.

2. 0부터 N - 1 까지 반복문을 통해 특정 원소(num[i])를 선택하고, 해당 원소를 제외한 except_num 리스트를 생성

3. except_num 리스트에서 투 포인터를 통해 두 원소의 합(sum)이 num[i] 인지 비교한다.
  t < num[i]이면 left를 증가
  t > num[i]이면 right를 감소

4. 2, 3 과정을 반복한다.




🤫  solution_1253

N = int(input())
num = list(map(int, input().split()))
num.sort()
cnt = 0

for i in range(N):
    except_num = num[:i] + num[i + 1:]
    left, right = 0, len(except_num) - 1
    while left < right:
        sum = except_num[left] + except_num[right]
        if sum == num[i]:
            cnt += 1
            break
        if sum < num[i]:
            left += 1  # t 를 증가시켜야 하므로 left 증가
        else:
            right -= 1  # t 를 감소시켜야 하므로 right 감소
print(cnt)

 

 

 

🧐 백준 11003 (최솟값 찾기,  슬라이딩 윈도우 이용)    Platinum V 

  


🤫  Algorithm 

특정 새로운 원소가 추가될 때 이 원소보다 큰 값은 유지될 필요가 없다.
(인덱스로 볼 때에도 새로 추가되는 원소가 가장 오래 남기 때문.)

덱으로 구성하고 덱의 앞에 항상 최소값이 오도록하기 위해
삽입은 append를 이용해 값과 인덱스로 진행한다.

1. 삽입하기전에 덱을 뒤에서 while로 삽입하려는 요소보다 큰값을 모두 제거.
(삽입한다면 덱은 항상 오름차순으로 정렬된 상태를 자동적으로 유지할 것이다.)
(이는 정렬로 인한 시간복잡도를 없애줘 시간초과가 발생하지 않게 할 수 있다.)

2. 추가적으로 슬라이딩 범위를 벗어난 요소들을 생각하면 아래와 같다.
삽입하기전에 덱을 앞에서부터 보며 인덱스 범위가 벗어난 요소들을 빼준다.

i) 뒤에서부터 확인하며 삽입하려는 요소보다 큰 값들을 제거
ii) 앞에서부터 보며 슬라이딩 범위를 벗어난 요소들을 제거
iii) 현재 요소 삽입
모든 연산이 O(1)으로 전체 배열을 확인하는데 O(n)으로 해결가능하다.




🤫  solution_11003

from collections import deque

N, L = map(int, input().split())
A = list(map(int, input().split()))
dq = deque()


for i in range(N):
    first, end = i - L, i
    # 현재 들어온 값보다 큰 값들을 제거하여 시간복잡도를 줄임
    while dq and dq[-1][0] > A[end]:  # 덱의 마지막 위치에서 현재값보다 큰 값 제거
        dq.pop()
    dq.append((A[end], end))  # A, A_idx, 마지막 위치에 현재값 저장
    if dq[0][1] <= first:
        dq.popleft()
    print(dq[0][0], end = " ")

 

 

🧐 에라토스테네스의 체   함수

def decimal(num):
    for i in range(2, int(num**0.5) + 1):
        if num % i == 0:
            return False
    return True​
for i in range(2, 100):
    if decimal(i):
        print(i, end = " ")​​


위의 과정을 통해 기존의 리스트에 누적하여 저장하는 방식보다 더욱 짧은 시간복잡도를 갖고 소수를 도출해 낼 수 있다.

 

🧐 회문 ( Palindrome )

회문(回文) 또는 팰린드롬(palindrome)은 거꾸로 읽어도 제대로 읽는 것과 같은 문장이나 낱말, 숫자, 문자열(sequence of characters) 등이다. 보통 낱말 사이에 있는 띄어쓰기나 문장 부호는 무시한다.

이런 회문에 대한 구현을 파이썬은 다른 언어와 다르게 간단하게 구현할 수 있다는 점에서 단연 파이썬의 매력이 돋보인다.
다음과 같이 회문을 구현할 수 있다.

[숫자인 경우 회문인지 확인하는 법]
a, b = map(int,input().split())  

# a~b범위 사이 회문 모두 구하기
palindrome = [i for i in range(a,b+1) if str(i) == str(i)[::-1]]

 

[문자인 경우 회문인지 확인하는 법]
string = input()

if string == string[::-1]:
    print("palindrome")​

 

# a,b=map(int,input().split())
# print('%0.9f'%(a/b))  // 절대,상대오차는 소수점 9번째 자리(0.1e-9)까지 허용

 

🧐 절대오차, 상대오차의  표현 

백준을 풀다보면 위로 갈수록 수학, 기하문제의 경우 다음과 같은 내용을 보는 경우가 종종 있다.
이에 대해 어떤 방식으로 출력을 해야할 지 갑자기 막힐 수 있기에 이번기회에 짚고 넘어가고자 한다.



다만, 10^-9 이하의 오차를 허용한다는 말이 꼭 소수 9번째자리까지만 출력하라는 것은 아니다.
예로  백준1008번을 예로 들겠다. 

소수점 9번째자리 보여주는 방법은 아래와 같다.
a, b = map(float,input().split()) 
print('%0.9f'%(a/b)) // 절대,상대오차는 소수점 9번째 자리(0.1e-9)까지 허용​​

소숫점 앞의 숫자는 전체 길이를 정한 문자열 공간 수를 의미함( 0은 정하지 않음을 의미 )
소숫점('.')뒤의 숫자9는 소숫점 뒤에 나올 숫자의 개수를 의미함

<자주쓰는 문자열 포맷 코드 종류 >
%s: 문자열(string)
%c: 문자 1개 (character)
%d:정수(integer) 
%f:부동소수(floating- point)                   

%%: 문자 %자체​

 

 

 

 

 

🧐 백준 1990 (소수인 회문)    Gold V 




🤫  solution_1990(시간초과)

import math, sys
input = sys.stdin.readline
a, b = map(int, input().split())

dec = [i for i in range(b+1)]
dec_first = []
for i in range(2, int(b**0.5) + 1):
    if i in dec:
        dec_first.append(i)
    dec = [j for j in dec if j%i != 0]
decimal = dec_first + dec

ans = []
def palindrome(num):
    string_num = str(num)
    num_len = len(string_num)
    if string_num[:num_len // 2] == string_num[math.ceil(num_len / 2):][::-1]:
        if num in decimal:
            ans.append(num)

for i in range(a, b+1):
    palindrome(i)
ans.append(-1)
print(*ans, sep = "\n")

 

🤫  solution_1990

a, b = map(int,input().split())

def decimal(num):
    for i in range(2, int(num**0.5) + 1):
        if num % i == 0:
            return False
    return True


#10,000,000이상인 팰린드롬수는 존재 X
if b > 10000000 :
    b=10000000

palindrome = [i for i in range(a,b+1) if str(i) == str(i)[::-1]]
for i in palindrome:
    if decimal(i):
        print(i)
print(-1)

 

 

 

 

🧐 백준 15927 (회문은 회문이 아니야!)    Gold V 




🤫  Algorithm 

1) 문자열 전체가 회문이 아니면 답은 문자열의 길이이다.
2) 문자열 전체가 회문일 때,
 i) 문자열 전체가 모두 같은 문자일 때 팰린드롬이 아닌 부분 문자열이 존재하지 않기에 답은 -1이다.
 ii) 처음 또는 끝의 문자 하나만 제외한 문자열은 팰린드롬이 아니기에 답은 (원래 문자열의 길이 - 1)이다.



🤫  solution_15927

string = input()

ans = 0
if string == string[::-1]:
    if string[:len(string) // 2 + 1] == string[:len(string) // 2 + 1][::-1]:
        ans = -1
    else:
        ans = len(string) - 1
else:
    ans = len(string)

print(ans)

 

 

 

 

🧐 백준 11664 (CCW, 외적)    Gold V 





🤫  Algorithm 

# 점 a를 선분의 왼쪽에 있는 점, 점 b를 선분의 오른쪽에 있는 점이라 하자.
# 점 a와 점 b의 중간점 m을 구한다.

# l = 점 a와 점 c의 거리
# r = 점 b와 점 c의 거리
# h = 점 m과 점 c의 거리

# 만약 h가 현재 존재하는 최소값 ans보다 작다면, ans를 h로 갱신해줍니다.
# 만약 l이 r보다 크다면, c가 a보다 b에 더 가깝게 있다는 뜻이므로 점 a를 점 m으로 바꿔준다.
# (이분탐색을 진행해야 하므로 더 가까운 쪽으로 범위를 좁혀줘야 하기 때문.)
# 반대로 r이 l보다 크다면 c가 b보다 a에 더 가깝게 있다는 뜻이므로 점 b를 점 m으로 바꿔준다.




🤫  solution_11664

ax,ay,az,bx,by,bz,cx,cy,cz = map(int,input().split())

ans = float('inf')

while True:
    mx,my,mz = (ax+bx)/2,(ay+by)/2,(az+bz)/2
    l = ((ax-cx)**2+(ay-cy)**2+(az-cz)**2)**0.5
    h = ((mx-cx)**2+(my-cy)**2+(mz-cz)**2)**0.5
    r = ((bx-cx)**2+(by-cy)**2+(bz-cz)**2)**0.5

    if abs(ans-h) <= 1e-6:
        print('%0.10f'%ans)
        exit()
    if h < ans:
        ans = h
    if l > r:
        ax,ay,az = mx,my,mz
    else:
        bx,by,bz = mx,my,mz

 

🧐 외적 관련 알고리즘, CCW ( Counter Clock Wise )

🤔 서로 다른 세점 사이의 넓이를 구하는 공식, CCW란?

CCW(Counter Clock Wise)는 다른 의미로 벡터의 외적값이라고도 한다.
(CCW의 절댓값 / 2)은 세 점의 벡터의 외적값 (세점으로 하는 삼각형 넓이)이다.
이때, 시계방향의 외적값은 음수의 넓이이고
반시계방향의 외적값은 양수의 넓이임을 알 수 있다.

 


CCW = (x1y2 + x2y3 + x3y1) - (x2y1 + x3y2 + x1y3)​

 

보통, 원점을 세 점중 하나에 포함시키게되면 미지수 개수가 줄어들어 계산이 편해진다.

- 이때, 행렬식으로 넓이를 구하는 공식은 다음과 같다. (우리가 익히 들어본 신발끈 공식이다.)

 

 

 

 

 

🧐 백준 11758 (CCW, 외적)    Gold V 




🤫  solution_11758

x1, y1 = map(int, input().split())
x2, y2 = map(int, input().split())
x3, y3 = map(int, input().split())

CCW = (x1*y2 + x2*y3 + x3*y1) - (x2*y1 + x3*y2 + x1*y3)

if CCW < 0:
    print(-1)
if CCW > 0:
    print(1)
if CCW == 0:
    print(0)

 

 

 

 

 

 

🧐 백준 2166 (CCW, 외적)    Gold V 


🤫  solution_2166

num = int(input())
x , y = [] , []
for i in range(num):
    X, Y = map(int,input().split())
    x.append(X)
    y.append(Y)

x.append(x[0])
y.append(y[0])

S = 0

for i in range(num):
    S += (x[i]*y[i+1] - x[i+1]*y[i])

print(round(abs(S)/2, 1))

 

 

 

 

 

 

 

 

 

🧐 백준 17386, 17387 (CCW, 외적)   Gold III, Gold II 




🤫  Algorithm 

https://velog.io/@jini_eun/%EB%B0%B1%EC%A4%80-17387%EB%B2%88-%EC%84%A0%EB%B6%84-%EA%B5%90%EC%B0%A8-2-Java-Python





🤫  solution_17387

import sys
input = sys.stdin.readline

x1, y1, x2, y2 = map(int, input().split())
x3, y3, x4, y4 = map(int, input().split())

point = []
point.append([x1, y1])
point.append([x2, y2])
point.append([x3, y3])
point.append([x4, y4])
A, B, C, D = point[0], point[1], point[2], point[3]


def ccw(p1, p2, p3):
    X1, X2, X3 = p1[0], p2[0], p3[0]
    Y1, Y2, Y3 = p1[1], p2[1], p3[1]
    CCW = (X1*Y2 + X2*Y3 + X3*Y1) - (X2*Y1 + X3*Y2 + X1*Y3)
    if CCW > 0:
        return 1
    elif CCW < 0:
        return -1
    else:
        return 0


def checkCross(a, b, c, d):
    result = 0
    p123, p124 = ccw(a, b, c), ccw(a, b, d)
    p341, p342 = ccw(c, d, a), ccw(c, d, b)

    if (p123 * p124 <= 0 and p341 * p342 <= 0) or (p123 * p124 == 0 and p341 * p342 == 0):
        if min(a[0], b[0]) <= max(c[0], d[0]) and \
                min(c[0], d[0]) <= max(a[0], b[0]) and \
                min(a[1], b[1]) <= max(c[1], d[1]) and \
                min(c[1], d[1]) <= max(a[1], b[1]):
            result = 1
    return result

print(checkCross(A, B, C, D))

 

🧐 너비 우선 탐색 ( Breadth First Search )

🤔 너비우선탐색, BFS란?

너비 우선 탐색, 즉 BFS는 그래프의 완전탐색(Exhaustive Search)기법 중 하나
- 너비 우선 탐색은 탐색 시작 노드와 가장 가까운 노드를 우선탐색한다.
- 이때, 목표노드에 도착하는 경로가 여러개라면, 최단경로를 보장한다.

BFS는 다음과 같은 특징이 있다.
기능 특징 시간복잡도 G(V, E)
그래프의 완전탐색 (Exhaustive Search)  Queue 자료구조를 이용한 FIFO 탐색 O(V+E)
BFS는 선입 선출 방식으로 탐색하는 방법이기에 큐를 이용해 구현한다.

 

🤔  BFS를 응용해 풀 수 있는 문제

- 가중치가 있는 그래프의 최단거리 (다익스트라 알고리즘과 같이 최단 경로 탐색 시 많이 활용)

🤔  BFS vs DFS
(어떤 상태에서의 답을 구하기 위해 어떤 경로를 따라 끝까지 들어가봐야 하는 경우에는 BFS로는 잘 안 된다.
마지막까지 일단 들어갔다가, 돌아오는 길에 거쳐갔던 정점(vertex)들에 대한 답을 순차적으로 갱신해줘야 하는 경우가 있다.
그리고 BFS로 풀 수 있다고 하더라도, DFS가 훨씬 직관적이고 구현하기 편한 경우들도 무수히 존재한다.)

 

 

🤔  BFS의 핵심 이론

BFS는 한 번 방문한 노드를 다시 방문하면 안된다.
따라서 노드 방문여부를 체크할 리스트가 필요하다.

BFS 탐색방식은 FIFO특성의 큐를 사용해 설명하자면, 아래와 같다.

1. BFS를 시작할 노드를 정한 후 사용할 자료구조 초기화 
- BFS를 위해 필요한 초기 작업 아래와 같다.
  ‣ 인접리스트로 그래프를 표현
  ‣ 방문리스트를 초기화하기
  ‣ 시작 노드 스택에 삽입하기
- 큐에 시작노드를 1로 삽입할 때, 해당 위치의 방문리스트를 체크하면 T, F, F, F, F, F 이다.


 
2. 큐에서 노드를 꺼내 인접 노드를 다시 큐에 삽입 
- 큐에서 노드를 꺼내면서 인접노드를 큐에 삽입한다.
- 이때, 방문 리스트를 체크하여 이미 방문한 노드는 큐에 삽입하지 않으며 꺼낸 노드는 탐색순서에 기록한다.
- 이때, 방문리스트는 T, T, T, F, F, F 이고 탐색 순서는 1이다.
1을 꺼낼 때 탐색순서에 1을 기록하고 인접노드 3, 2를 큐에 삽입하며 방문리스트에 체크한다.

 

3. 큐에 값이 없을 때까지 반복 
- 앞선 과정을 큐 자료구조에 값이 없을 때까지 반복하며 이미 거친 노드는 재삽입 하지 않는 것이 핵심!
- FIFO 방식으로 탐색하기에 탐색 순서가 DFS와 다름을 유의해야 한다!
- 이때, 최종적인 탐색순서는 1 - 2 - 3 - 5 - 6 - 4 이다.


2, 3 순서로 노드를 꺼내며 인접 노드를 큐에 삽입한다.
2의 경우 5, 6은 아직 방문하지 않았기에 방문리스트를 체크하며 모두 삽입한다.
3의 경우 4 역시 방문한 적이 없어서 방문리스트를 체크하며 삽입한다.
이때, 탐색순서는 2, 3이 기록된다.

5와 6을 꺼낼 때, 인접노드가 없기에 탐색순서에 기록만 하고 꺼낸다.
4를 꺼낼 때 인접노드는 6이지만 이미 앞서 방문했기에 6은 삽입하지않고 꺼내기만 한다.

 

 

 

🤔 BFS 수도코드

BFS의  구현

BFS를 구현하는 경우의 수도 코드는 다음과 같다.

BFS(너비 우선 탐색)는 시작 정점(vertex)으로부터 거리가 가까운 정점들을 먼저 차례로 방문하고
멀리 떨어져 있는 정점을 나중에 방문하는 운행 방법이다. (점차 영역을 넓혀나간다고 생각하면 쉽다.)
BFS는 DFS보다 쓰임새는 적지만, 다익스트라 알고리즘과 같이 최단 경로 탐색 시 많이 활용된다.
BFS는 재귀로 구현되지 않고 오직 
큐를 이용한 반복 구조를 통해 구현된다는 점을 명심하자. 

<pseudocode>

BFS(G, start_v)
    let Q be a queue
    mark start_v as visited
    Q.enqueue(start_v)
    while Q is not empty do
        v := Q.dequeue()
        if v is the goal then
            return v
        for all edges from v to w in G.adjacentEdges(v) do
            if w is not marked as visited then
                label w as visited
                w.parent := v
                Q.euqueue(w)

 

수도코드를 살펴보면 하나의 정점에 대한 인접 간선을 모두 추출하고 도착점인 정점을 큐에 삽입하는 방식으로 동작한다.
코드의 전개는 앞의 DFS 스택 구현과 크게 다르지 않다.
다만, 여기서는 FIFO 구조의 큐를 활용했기 때문에 너비 우선 탐색, 즉 가까운 정점부터 모두 탐색해두고 그 다음 거리의 정점으로 탐색 범위를 넓혀나가는 운행 방식이 가능해지는 것이다.

<python code>

def bfs(start_v):
    visited = [start_v]
    Q = deque([start_v])
    while Q:
        v = Q.popleft()
        for w in graph[v]:
            if w not in visited:
                visited.append(w)
                Q.append(w)

    return visited

 

 

 

 

 

효율성

- 앞서 언급했던 DFS와 다르지 않다.

  • 시간 복잡도
인접 행렬의 경우 하나의 정점 당 n번 check를 해주어야 하기 때문에 O(n^2)이다.
인접 리스트의 경우 정점 방문 & 해당 정점의 인접 정점 방문이므로 O(n+m)이다.
시간 복잡도의 경우에는 결국엔 어떤 정점을 탐색하느냐의 문제이기 때문에 공간 복잡도와 동일하다.

 

  • 공간 복잡도
공간 복잡도의 경우, 그래프와 visited, 스택을 모두 고려해주어야 하기에 그래프의 공간복잡도는 다음과 같다.
인접 행렬의 경우에는 하나의 정점과 모든 정점 간의 연결 관계를 다 표현해주어야 하기 때문에 O(n^2)이다.
인접 리스트의 경우에는 그래프 내 정점 개수를 n, 간선 개수를 m이라고 할 때 O(n+m)이다.

결과적으로 공간복잡도는 그래프의 공간복잡도를 따라 가게 된다.

BFS의 효율성에 대해 말할 경우, 스택과 visited는 모두 O(n)의 공간복잡도를 가지고 있다 (정점 개수 n).
이는 그래프가 인접 행렬이나 인접 리스트로 구현되어 있든 두 가지 경우 모두에 해당한다.

 

 
 
 

🧐 백준 1260 (DFS와  BFS)



🤔
 Algorithm 과정_ DFS 

1. 그래프를 인접리스트로 저장한다. (무방향 그래프이기에 양쪽방향으로 간선을 모두 저장한다.)
2. 방문리스트를 모두 False로 초기화한다.
3. 재귀를 이용한 DFS를 수행하는데, 아래와 같이 진행한다.



🤔 Algorithm 과정_ BFS 
1. 그래프를 인접리스트로 저장한다. (무방향 그래프이기에 양쪽방향으로 간선을 모두 저장한다.)
2. 방문리스트를 모두 False로 초기화한다. (이때, 위의 DFS에서 사용했기에 초기화를 반드시 해줘야 한다!)
3. BFS를 수행하는데, 아래와 같이 진행한다.

이를 풀이하면 다음과 같다.

1. 큐 자료구조에 starting_vertex 삽입
visit_list에 현재 vertex 기록

2. while (큐가 빌 때 까지):
	큐에서 vertex를 가져와 출력
    현재 vertex의 연결vertex 중 미방문 vertex를 큐에 삽입(append)
    방문리스트에 기록



🤫  solution_1260

import sys
input = sys.stdin.readline

V, E, starting_vertex = map(int, input().split())

# graph라는 인접리스트 생성
#         [1]      [2]     [3]       [4]
# [[], [2, 3, 4], [1, 4], [1, 4], [1, 2, 3], []]
graph = [[] for _ in range(V+1)]
for _ in range(E):
    u, v = map(int, input().split())
    graph[u].append(v)
    graph[v].append(u)

# 정점에 우선순위가 부여되었으므로 정렬
for i in range(V+1):
    graph[i].sort()

# 방문리스트 생성
visit_list = [False] * (V+1)

def DFS(v):
    print(v, end = " ")
    visit_list[v] = True
    for i in graph[v]:
        if not visit_list[i]:
            DFS(i)

from collections import deque
def BFS(v):
    queue = deque()
    queue.append(v)
    visit_list[v] = True
    while queue:
        curr_vertex = queue.popleft()
        print(curr_vertex, end = " ")
        for i in graph[curr_vertex]:
            if not visit_list[i]:
                visit_list[i] = True
                queue.append(i)


# DFS 출력
DFS(starting_vertex)
print()

visit_list = [False] * (V+1)    # 리스트 초기화
# BFS 출력
BFS(starting_vertex)

 

🧐 깊이 우선 탐색, DFS (Depth First Search)

https://chan4im.tistory.com/121

 

self.winter.(09-3). DFS (깊이 우선 탐색) 개념 _ 백준 11724(connected component)

🧐 깊이 우선 탐색 ( Depth First Search ) 🤔 깊이우선탐색, DFS란? 깊이 우선 탐색, 즉 DFS는 그래프의 완전탐색(Exhaustive Search)기법 중 하나 - 그래프의 시작노드에서 출발해 탐색할 한쪽 분기를 정한

chan4im.tistory.com

 

 

🧐 백준 2023 (DFS)   Gold V 


🤔
 Algorithm 과정 

1. 한자리소수 -> 이 한자리 소수에서 파생되어 나온 두자리소수 -> ... 의 반복이므로
2. 한자리 소수는 [2, 3, 5, 7]이다.
3. 여기서 파생되는 소수는 다음과 같다.
[2] - 23, 29
[3] - 31, 37
[5] - 53, 59
[7] - 71, 73, 79
위 과정을 계속 반복하여 입력된 자리수에 맞는 출력을 진행해주면 된다.
이는 재귀를 이용한 DFS를 통해 해결할 수 있으며 아래 그림을 통해 간단히 도식화 하였다.


🤫  solution_2023 (시간초과)

import sys, math
sys.setrecursionlimit(10000)
input = sys.stdin.readline

N = int(input())

dec = [i for i in range(2, 8*10**(N))]
dec_front = []
for i in range(2, int(math.sqrt(10**(N+1))) + 1):
    if i in dec:
        dec_front.append(i)
    dec = [j for j in dec if j % i != 0]
decimal = dec_front + dec

single_decimal = [2, 3, 5, 7]

def DFS(num):
    if len(str(num)) == N:
        print(num)
    else:
        for i in range(1, 10, 2):
            if num*10 + i in decimal:
                DFS(num*10 + i)

for i in single_decimal:
    DFS(i)



🤫  solution_2023

import sys
sys.setrecursionlimit(10000)
input = sys.stdin.readline

N = int(input())
single_decimal = [2, 3, 5, 7]

def decimal(num):
    for i in range(2, num//2 + 1):
        if num % i == 0:
            return False
    return True

def DFS(num):
    if len(str(num)) == N:
        print(num)
    else:
        for i in range(1, 10, 2):
            if decimal(num*10 + i):
                DFS(num*10 + i)

for i in single_decimal:
    DFS(i)

cf. // 는 정수형 나눗셈이다. 

 

 

 

🧐 백준 13023 (DFS)   Gold V 


🤔
 Algorithm 과정 

1. 그래프를 인접리스트로 저장한다. (무방향 그래프이기에 양쪽방향으로 간선을 모두 저장한다.)
2. 방문리스트를 모두 False로 초기화한다.
3. DFS를 수행하는데, 아래와 같이 진행한다.
4. 최종적으로 DFS의 재귀 횟수가 5회 이상이면 되므로 5회가 된 순간이 바로 임계점이다.
5. 따라서 재귀횟수가 5회면 바로 1을 출력하고 5회 미만이라면 0을 출력한다.


🤫  solution_13023

# 백준 11724
# 친구 관계 5개를 찾는 문제 = 재귀 깊이가 5번 이상이면 된다.
import sys
input = sys.stdin.readline

V, m = map(int, input().split())
vertex = [[] for _ in range(V+1)]

# 방문 리스트 생성
visit_list = [False] * (V+1)

for _ in range(m):
    u, v = map(int, input().split())
    vertex[u].append(v)
    vertex[v].append(u)
# print(vertex) => [[1], [0, 2], [1, 3], [2, 4], [3], []]
# 인접 리스트 생성       [0]   [1]     [2]     [3]    [4]

threshold = False
def DFS(curr_vertex, depth):
    global threshold
    if depth == 5:
        threshold = True
        return
    visit_list[curr_vertex] = True
    for i in vertex[curr_vertex]:
        if not visit_list[i]:
            DFS(i, depth+1)
    visit_list[curr_vertex] = False

for i in range(V):
    DFS(i, 1)
    if threshold:
        break

if threshold:
    print(1)
else:
    print(0)

 

 

 

🧐 깊이 우선 탐색 ( Depth First Search )

🤔 깊이우선탐색, DFS란?

깊이 우선 탐색, 즉 DFS는 그래프의 완전탐색(Exhaustive Search)기법 중 하나
- 그래프의 시작노드에서 출발해 탐색할 한쪽 분기를 정한다.
- 최대 깊이까지 탐색을 마치면 다른 분기로 이동해 다시 탐색을 진행하는 알고리즘이다.

DFS는 다음과 같은 특징이 있다.
기능 특징 시간복잡도 G(V, E)
그래프의 완전탐색 (Exhaustive Search)  재귀함수나 스택 자료구조로 구현 O(V+E)
DFS는 재귀함수의 이용으로 stack overflow현상에 대해 주의해야 한다.

 

🤔  DFS를 응용해 풀 수 있는 문제

- 백트래킹(Backtracking) [https://www.acmicpc.net/step/34]
- 단절점(Articulation Point) 찾기  [https://www.acmicpc.net/problem/11266]
- 단절선 찾기 [https://www.acmicpc.net/problem/11400]
- 사이클 찾기
- 위상 정렬(topological sort) [https://www.acmicpc.net/step/25]

 

🤔  DFS의 핵심 이론

DFS는 한 번 방문한 노드를 다시 방문하면 안된다.
따라서 노드 방문여부를 체크할 리스트가 필요하다.

DFS 탐색방식은 LIFO특성의 스택을 사용해 설명하자면, 아래와 같다.

1. DFS를 시작할 노드를 정한 후 사용할 자료구조 초기화 
- DFS를 위해 필요한 초기 작업 아래와 같다.
  ‣ 인접리스트로 그래프를 표현
  ‣ 방문리스트를 초기화하기
  ‣ 시작 노드 스택에 삽입하기
- 스택에 시작노드를 1로 삽입할 때, 해당 위치의 방문리스트를 체크하면 T, F, F, F, F, F 이다.

 
2. 스택에서 노드를 꺼내 인접 노드를 다시 스택에 삽입 
- 이제 pop을 수행하여 노드를 꺼내고 꺼낸 노드를 탐색순서에 기록한다.
- 인접리스트의 인접노드를 스택에 삽입하며 방문리스트를 체크한다.
- 이때, 방문리스트는 T, T, T, F, F, F 이다.

 

3. 스택에 값이 없을 때까지 반복 
- 앞선 과정을 스택자료구조에 값이 없을 때까지 반복하며 이미 거친 노드는 재삽입 하지 않는 것이 핵심!

 

 

 

🤔 DFS 수도코드

DFS의 재귀 구현

DFS를 재귀로 구현하는 경우의 수도 코드는 다음과 같다.

쉽게 말하자면 정점을 방문하되 방문되었다고 표시하고 해당 정점의 인접 간선을 모두 탐색하는 것.
이때, 해당 정점이 방문되지 않았다면 해당 정점에 대해서 다시 DFS를 call하는 형태

정점의 인접 간선을 하나씩 탐색 후 탐색이 불가능하면 빠져나와 다른 인접 간선을 이와 동일한 방식으로 탐색진행

<pseudocode>

DFS(G, v)
    visit v;
    mark v as visited;
    for all directed edges from v to w that are in G.adjacentEdges(v) do
        if vertex w is not marked as visited then
            recursively call DFS(G, w)

<python code>

 

def recursive_dfs(v, visited=[]):
    visited.append(v)
    for w in graph[v]:
        if not w in visited:
            visited = recursive_dfs(w, visited)
    return visited

이때, graph는 global 변수로 작용하게 되고, visited는 함수의 인자로 계속 넣어주는 형태이다.

이로 인해 visited를 리턴해서 계속 누적해주는 방식으로 진행을 해줘야한다.

 

 

DFS의 스택 구현

스택을 이용하는 경우에는 반복문을 이용해 구현할 수 있다.
스택을 이용해 모든 인접 간선을 추출하고 다시 도착점인 정점을 스택에 삽입하는 구조로 구현이 가능하다. 

 

<pseudocode>

DFS_iterative(G,v)
    let S be a stack
    S.push(v)
    while S is not empty do
        v = S.pop()
        if v is not marked as visited then
            mark v as visited
            for all edges from v to w in G.adjacentEdges(v) do
                S.push(w)

스택에 한 번에 모든 인접 간선의 정점을 다 push하기 때문에 자칫 BFS로 오해하기 쉽다.

여기서 염두해야할 점은 stack에 push될 때가 아니라 pop이 되면서 방문을 check하는 구조이다.

따라서 정점 방문 순서로 본다면 확실히 DFS임을 알 수 있다.

 

<python code>

def iterative_dfs(start_v):
    visited = []
    stack = [start_v]
    while stack:
        v = stack.pop()
        if v not in visited:
            visited.append(v)
            for w in graph[v]:
                stack.append(w)

return visited

 

효율성

  • 시간 복잡도
인접 행렬의 경우 하나의 정점 당 n번 check를 해주어야 하기 때문에 O(n^2)이다.
인접 리스트의 경우 정점 방문 & 해당 정점의 인접 정점 방문이므로 O(n+m)이다.
시간 복잡도의 경우에는 결국엔 어떤 정점을 탐색하느냐의 문제이기 때문에 공간 복잡도와 동일하다.

 

  • 공간 복잡도
공간 복잡도의 경우, 그래프와 visited, 스택을 모두 고려해주어야 하기에 그래프의 공간복잡도는 다음과 같다.
인접 행렬의 경우에는 하나의 정점과 모든 정점 간의 연결 관계를 다 표현해주어야 하기 때문에 O(n^2)이다.
인접 리스트의 경우에는 그래프 내 정점 개수를 n, 간선 개수를 m이라고 할 때 O(n+m)이다.

결과적으로 공간복잡도는 그래프의 공간복잡도를 따라 가게 된다.

DFS의 효율성에 대해 말할 경우, 스택과 visited는 모두 O(n)의 공간복잡도를 가지고 있다 (정점 개수 n).
이는 그래프가 인접 행렬이나 인접 리스트로 구현되어 있든 두 가지 경우 모두에 해당한다.

 

 
 
 

🧐 백준 11724 (Connected Component)

- connected component 즉, 연결요소는 다음과 같은 조건에서 성립한다.

  • 연결요소에 속한 모든 정점(vertex)을 연결하는 간선(edge)가 있어야 한다.
  • 또 다른 연결요소에 속한 정점(vertex)과 연결하는 간선(edge)이 있으면 안된다.

🤔
 Algorithm 과정 

0.  노드의 최대 개수가 1000이므로 시간복잡도 n^2이하의 알고리즘을 모두 사용할 수 있다.
1. 그래프를 인접리스트로 저장한다. (무방향 그래프이기에 양쪽방향으로 간선을 모두 저장한다.)
2. 방문리스트를 모두 False로 초기화한다.
3. DFS를 수행하는데, 아래와 같이 진행한다.


🤫  solution_11724

import sys
sys.setrecursionlimit(10000)
input = sys.stdin.readline

V, E = map(int, input().split())
CC = [[] for _ in range(V+1)]  # [[], [], [], [], [], [], []]
visit_list = [False] * (V+1)

for _ in range(E):
    u, v = map(int, input().split())
    # 양방향 간선이므로 양쪽에 정점 더하기.
    CC[u].append(v)
    CC[v].append(u)
# print(CC) => [[], [2, 5], [1, 5], [4], [3, 6], [2, 1], [4]]
# 인접리스트 생성         [1]     [2]   [3]    [4]     [5]   [6]


def DFS(v):
    visit_list[v] = True
    for i in CC[v]:
        if not visit_list[i]: # 연결요소가 방문리스트에 F로 되어있으면 True로 고치기 위한 재귀
            DFS(i)

cnt = 0
for i in range(1, V+1):
    if not visit_list[i]:  # 1 2 3 4 5 6의 방문리스트에 대해 방문한적이 없다면
        cnt += 1
        DFS(i)

print(cnt)

 

 

 

🧐 백트래킹 ( Backtracking )

🤔 백트래킹이란?

DFS의 일종인 백트래킹은 일반적인 DFS와 달리 가지치기(pruning)를 진행
모든 경우의 수를 탐색하는 대신 조건을 거는데, 다음과 같다.
- 유망(promising)하지 않은 경우에는 탐색을 중지하고 이전 노드로 돌아가서 다른 경우를 탐색한다.
백트래킹은 DFS, BFS 두가지로 구현이 가능하지만 일반적으로 DFS로 구현하는것이 더 편하다.
(BFS는 큐의 크기가 커질 수 있기 때문에 보통 일반적으로 DFS를 사용한다)
다만 트리의 깊이가 무한대가 될 때, BFS를 사용한다.
또한 일반적으로 DFS는 스택이나 재귀로, BFS는 큐로 구현한다.

백트래킹은 답이 될 수 없는 후보는 더이상 깊게 들어가지 않고 되돌아가는 방법이다.
 

 

 

🤔 백트래킹 수도코드

procedure backtracking(c) is
    if reject(P, c) then return
    if accept(P, c) then output(P, c)
    s ← first(P, c)
    while s ≠ NULL do
        backtracking(s)
        s ← next(P, s)
reject(P,c): 만약 다음의 노드가 후보가 아닌다면 return으로 종료.
accept(P,c): 만약 현재 노드까지가 답과 일치한다면 답을 출력.
first(P,c): 후보 C의 첫번째 확장을 시작한다.
답이 없을때까지 다음 함수를 계속 반복.

 

 

 

🧐 백준 15649


🤫 해결의 실마리 .  백트래킹

DFS의 방법 중 하나인 백트래킹을 이용한다.
이때, 알고리즘의 핵심은 스택을 이용한 구현에서 함수의 호출방식이 스택의 동작방식임을 이용한다.
함수의 호출 = push,  함수의 종료 = pop과 같은 방식이기 때문이다.



🤔
 Algorithm 과정 
1. 해당하는 경우를 스택에 push하고 동작이 끝나면 다시 빼내는 pop을 진행
2. 이렇게 진행할 때, 이미 선택한 숫자는 배제 (1 1은 안되므로)해야 하므로 가지치기(pruning)

 

🤫  solution_15649 (Backtracking)

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

stack = []
def f():
    if len(stack) == m:
        print(" ".join(map(str, stack)))
        return


    for i in range(1, n+1):
        if i in stack:
            continue
        stack.append(i)
        f()
        stack.pop()

f()



🤫  solution_15649 (itertools의  permutations 사용)

from itertools import permutations
n, m = map(int, input().split())
num = [i for i in range(1, n+1)]

ans = permutations(num, m)

for i in ans:
    for j in i:
        print(j, end=' ')
    print()

 

 

 

🧐 백준 15650~15652


🤫  solution_15650

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

stack = []
first = 1
def f(first):
    if len(stack) == m:
        print(" ".join(map(str, stack)))
        return


    for i in range(first, n+1):
        if i in stack:
            pass

        stack.append(i)
        f(i+1)
        stack.pop()
f(first)





🤫  solution_15651

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

stack = []
def f():
    if len(stack) == m:
        print(" ".join(map(str, stack)))
        return


    for i in range(1, n+1):
        stack.append(i)
        f()
        stack.pop()
f()




🤫  solution_15652

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

stack = []

first = 1
def f(first):
    if len(stack) == m:
        print(" ".join(map(str, stack)))
        return

    for i in range(first, n+1):
        stack.append(i)
        f(i)
        stack.pop()
f(first)​

 

 

 

 

🧐 딕셔너리 (Dictionary). { key : value }

🤔 딕셔너리 개념

key-value를 하나의 쌍으로 갖는 자료구조로 다른 언어에의 map과 같은 기능을 한다.

이때, 딕셔너리는 키가 중복되는것을 허용하지 않는다!

이때, 딕셔너리는 키가 중복되는것을 허용하지 않는다!

 

 

🤔 딕셔너리의 생성과 삭제

dict_a['name'] = 'b'
dict_b[3] = 2

del dict_a['name']  # del a[key]

 

🤔 딕셔너리 key, value에 대한 접근-1

dict_a.keys() 	# key 객체들을 return
dict_a.values() # value 객체들을 return 
dict_a.items()	# key-value 쌍을 return

 

🤔 딕셔너리 key, value에 대한 접근-2. (key로 value얻기, value로 key얻기)

dict_a.get(key)  # key에 해당하는 "value값 출력"

for i in dict_a  			# 기본적으로 for문에서 i를 통해 key값을 사용할 수 있음
for i in dict_a.values()    # 기본적으로 for문에서 i를 통해 value값을 사용할 수 있음

for key, value in dict_a.items():  # key와 value를 한번에 사용
for key, value in dict_a: 		   # 위와 동일한 사용법

 

😶 활용 

for i in construct.keys():
    if construct[i] == n:  # key의 어떤 값이 n과 같다면
        ans.append(i)      # {construct[i] : i} key에 해당하는 value, i를 추가

 

 

 

🧐 백준 2798  (딕셔너리 활용)

🤫 해결의 실마리 1. brute force (완전탐색)

처음부터 끝까지 모두 탐색하는 방법의 brute force를 사용한다,.

🤫 해결의 실마리 2.  딕셔너리와 set 

cf. 딕셔너리 객체의 get()함수

dict.get(key, default = None) 
# 매개변수로 넘긴 값 (괄호 안의 값)이 키에 속하지 않으면? None 출력

get함수의 return값은 첫번째 인자의 키값이다.



🤔
 Algorithm 과정 
1. 입력받은 숫자 리스트들에서 3개의 숫자를 골라 더한 값을 set에 집어넣는다.
2. 이 sum_set에 대해 입력된 M을 넘지 않는 것들에 대해 딕셔너리 형태로 세 숫자의 합과 m과의 차이를 저장한다.
3. 이때, 저장한 m과의 차이가 가장 작은 것이 m과 가장 가까운 것이므로 답임을 알 수 있다.
 

🤫  solution_2798

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

sum_set = set()
sum_dict = {}

for i in range(n):
    for j in range(i+1, n):
        for k in range(j+1, n):
            sum_set.add(num[i] + num[j] + num[k])

for i in sum_set:
    if m - i >= 0:
        sum_dict[i] = m - i
    else:
        pass

print(min(sum_dict, key = sum_dict.get))

 

 

🧐 백준 2231  (딕셔너리 활용)

🤫 해결의 실마리 1. 자연수의 자릿수에 해당하는 숫자 구하기.

# 각 자릿수의 숫자의 합을 출력하는 함수
def digit(n):
    if n < 10:
        return n
    else:
        return digit(n // 10) + digit(n % 10)


🤫 해결의 실마리 2.  딕셔너리의 사용 

construct = {}
for i in range(n+1):
    construct[i] = i + digit(i)

ans = []

for i in construct:
    if construct[i] == n:
        ans.append(i)



🤔 Algorithm 과정 
216 => 198 + 1 + 9 + 8  ... 이런 방식은 거의 불가 (역추적 진행 시 생성자가 여러개임을 도출할 수 없음)

맵핑을 통해 1~N까지 똑같은 value들 중 최소 key를 찾는 방식으로 문제를 해결해 나갈 것이다.

{1:1}
{2:2}
...
{13:17}
{14:19}
{15:21}
...
{198:216}
...
{N:N'}

 

🤫  solution_2231

n = int(input())

# 각 자릿수의 숫자의 합을 출력하는 함수
def digit(n):
    if n < 10:
        return n
    else:
        return digit(n // 10) + digit(n % 10)

construct = {}
for i in range(n+1):
    construct[i] = i + digit(i)

ans = []

for i in construct:
    if construct[i] == n:
        ans.append(i)
print(0) if len(ans) == 0 else print(min(ans))

 

 

🧐 백준 7568 (C++의 pair와 같은 자료구조가 필요해! 

🤫 해결의 실마리.  pair 처럼 입력받은 key와 value가 겹쳐도 되는 자료구조를 생성하자!.

for _ in range(int(input())):
    w, h = map(int, input().split())
    person.append((w, h))
# print(person)  => [(55, 185), (58, 183), (88, 186), (60, 175), (46, 155)]




🤔 Algorithm 과정 
1. 각각 받은 w, h에 대해 등수는 1부터 시작하므로 rank = 1로 초기화해준다.
2. 입력한 변수들을 저장한 person리스트에 대해
0번째 인덱스(몸무게)와 1번째 인덱스(키)가 모두 큰 경우에 rank를 1 증가시켜준다.

 

🤫  solution_7568

person = []

for _ in range(int(input())):
    w, h = map(int, input().split())
    person.append((w, h))

# print(person)  => [(55, 185), (58, 183), (88, 186), (60, 175), (46, 155)]


for i in person:
    rank = 1
    for j in person:
        if i[0] < j[0] and i[1] < j[1]:
            rank += 1
    print(rank, end = " ")

 

 

🧐 백준 1436

🤔 Algorithm 과정 
1. 처음 666인 수를 terminal변수에 저장
2. n이 0이 아닐 때 까지 반복하는데, 문자열 terminal을 update하는 방식으로 문제를 해결할 것이다.
3. terminal 문자열 안에 666이 들어있다면, n을 1만큼 감소시키고 
4. 만약 n이 0이라면 반복문을 탈출한다.
5. 이후 terminal의 값을 1만큼 증가시킨다.
 

🤫  solution_1436

n = int(input())

terminal = 666
while n != 0:
    if '666' in str(terminal):
        n -= 1
        if n == 0:
            break
    terminal += 1
print(terminal)

 

 

🧐 백준 1018 (CNN 알고리즘)

🤫해결의 실마리. CNN Algorithm (Convolution Neural Network)

CNN에서 filter를 이용해 합성곱 계층의 동작처럼 8X8의 정답 체스판을 만들고 
CNN알고리즘처럼 입력받은 체스판을 스캔하면서 틀린 경우를 카운팅해서 틀린경우의수가 최소를 출력한다.


🤔 CNN_Algorithm 과정 
https://wikidocs.net/164823

 

🤫  solution_1436

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

chess = [list(input()) for _ in range(n)]
ans_chess_W , ans_chess_B = [], []
for i in range(4):
    ans_chess_W.append('WBWBWBWB')
    ans_chess_W.append('BWBWBWBW')
    ans_chess_B.append('BWBWBWBW')
    ans_chess_B.append('WBWBWBWB')


def wrongcount_W (x, y):
    cnt = 0
    for i in range(8):
        for j in range(8):
            if chess[x+i][y+j] != ans_chess_W[i][j]:
                cnt += 1
    return cnt

def wrongcount_B (x, y):
    cnt = 0
    for i in range(8):
        for j in range(8):
            if chess[x+i][y+j] != ans_chess_B[i][j]:
                cnt += 1
    return cnt

# CNN처럼 8x8 size로 체스판 자르기
cnt = []
for i in range(n-8 +1):
    for j in range(m-8 +1):
        cnt.append(wrongcount_W(i, j))
        cnt.append(wrongcount_B(i, j))
print(min(cnt))

 

🧐 완전 탐색 (Exhaustive Search)

🤫 완전 탐색이란?

이름에서도 알 수 있듯 하나부터 열까지 모든 경우를 다 탐색하는 알고리즘이다.
모든 경우를 탐색하니 당.연.히 정답을 찾을 수 있다.

장점:
 모든 경우를 고려해서 정답을 확실히 찾을 수 있으며 복잡하지 않고 빠르게 구현이 가능
단점: 당연하게도 모든 경우를 다 찾기에 효율적이지 않고 실행시간이 오래걸린다.


🤫  완전탐색의 종류 

🤔브루트포스(https://www.acmicpc.net/step/22)
- 
조건문/반복문을 이용해 모든 경우의 수를 찾는 방법
- https://chan4im.tistory.com/119

 

브루트 포스 단계

체스판을 만드는 모든 경우를 시도하여 최적의 방법을 찾는 문제

www.acmicpc.net

 

🤔백트래킹(https://www.acmicpc.net/step/34)
- 현재 상태에서 가능한 후보군으로 가지를 치며 탐색하는 방법

 

백트래킹 단계

조금 더 복잡한 백트래킹 문제 1

www.acmicpc.net

 

🤔BFS,DFS(https://www.acmicpc.net/workbook/view/1833)

- BFS(너비 우선 탐색): 정점과 같은 레벨에 있는 형제노드를 탐색

- DFS(깊이 우선 탐색): 정점의 자식노드들을 탐색

아래 문제집에서는 완전탐색의 유명한 예시인 순열도 포함되어 있다.
순열과 조합 : (https://buyandpray.tistory.com/52)

 

문제집: DFS, BFS 추천문제 (c3171700)

 

www.acmicpc.net

 

🤔비트마스크(https://www.acmicpc.net/workbook/view/804)
- 2진수인 컴퓨터의 연산을 이용하는 방법

 

문제집: 비트마스크 (cokcjswo)

 

www.acmicpc.net

 

+ Recent posts