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

 

9663번: N-Queen

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다. N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.

www.acmicpc.net


문제 해결을 위한 과정

이 문제의 경우 백트래킹을 통해 해결할 수 있는 문제였습니다. Queen의 경우 같은 열 혹은 같은 행에 있거나 같은 대각선에 있는 곳을 이동할 수 있습니다. 따라서 다음 그림과 같이 퀸을 놓을 수 있습니다. (단, n이 4 인 경우)

색을 칠한 곳이 queen의 위치

같은 행에는 다시 queen을 위치할 수 없고, 시간복잡도를 위해 2차원 배열을 1차원 배열로 단순화해서 표시할 수 있습니다. 위 그림의 경우는 다음과 같습니다. arr = [1, 3, 0, 2] 해석하면 0번째 인덱스(행)의 1번째 인덱스(열), 1번째 인덱스(행)의 3번째 인덱스(열), 2번째 인덱스(행)의 0번째 인덱스(열), 3번째 인덱스(행)의 2번째 인덱스(열)에 위치했다는 뜻입니다. 이를 완전탐색 식으로 표현하면 아래의 코드와 같습니다. 중요한 검사 로직은 같은 열에 있는지(check함수의 첫 번째 if문), 대각선에 있는지(check 함수의 두 번째 if문)으로 검사합니다. 이때 같은 행인 경우는 존재할 수 없는데 배열의 인덱스 자체가 행을 의미하기 때문에 같은 행에는 queen이 무조건 없습니다.


소스코드
n = int(input())
arr = [0] * n
count = 0

def dfs(depth):
  global count
  if depth == n:
    count += 1
    return

  for i in range(n):
    arr[depth] = i
    if check(depth):
      dfs(depth + 1)

def check(col):
  for i in range(col):
    if arr[i] == arr[col]:
      return False
    elif abs(i-col) == abs(arr[i]-arr[col]):
      return False
  return True

dfs(0)
print(count)

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

 

14891번: 톱니바퀴

첫째 줄에 1번 톱니바퀴의 상태, 둘째 줄에 2번 톱니바퀴의 상태, 셋째 줄에 3번 톱니바퀴의 상태, 넷째 줄에 4번 톱니바퀴의 상태가 주어진다. 상태는 8개의 정수로 이루어져 있고, 12시방향부터

www.acmicpc.net


문제 해결을 위한 과정

이 문제의 경우 역시 마찬가지로 구현 문제이므로 차분하게 조건을 작성하면 해결할 수 있었다. 돌리게 되는 톱니를 기준으로 좌측 영역, 우측 영역을 분리하여 각자 맞게 회전을 시켜주었다. 다만 이때 flagLeft(왼쪽에 맞물리는 부분) flagRight(우측에 맞물리는 부분)을 for문 내부에 그대로 넣어서 덮어진 값을 사용하게 되어 틀렸습니다. 를 받게 되었다. 따라서 tempFlagLeft, tempFlagRight로 분리하여 덮어씌워짐을 방지 하였다.

소스코드
arr = [[] for _ in range(4)]
for i in range(4):
   tempArr = input()
   for j in range(len(tempArr)):
       arr[i].append(int(tempArr[j]))
k = int(input())
moves = []
for _ in range(k):
    a, b = map(int, input().split())
    moves.append((a, b))

def turn_right(array):
    right = array[7]
    for i in range(6, -1, -1):
        array[i + 1] = array[i]
    array[0] = right

def turn_left(array):
    left = array[0]
    for i in range(0, 7):
        array[i] = array[i + 1]
    array[7] = left

for x in range(k):
    num, dir = moves[x]
    num -= 1 # index 맞추기 위함
    flagLeft = arr[num][6]
    flagRight = arr[num][2]
    if dir == 1:
        turn_right(arr[num])
    else:
        turn_left(arr[num])
    tempDir = dir
    tempFlagLeft = flagLeft
    tempFlagRight = flagRight
    for i in range(num-1, -1, -1):
        if tempFlagLeft != arr[i][2]:
            if tempDir == 1:
                tempFlagLeft = arr[i][6]
                tempFlagRight = arr[i][2]
                turn_left(arr[i])
                tempDir *= -1
            else:
                tempFlagLeft = arr[i][6]
                tempFlagRight = arr[i][2]
                turn_right(arr[i])
                tempDir *= -1
        else:
            break
    tempDir = dir
    tempFlagLeft = flagLeft
    tempFlagRight = flagRight
    for i in range(num + 1, 4):
        if tempFlagRight != arr[i][6]:
            if tempDir == -1:
                tempFlagLeft = arr[i][6]
                tempFlagRight = arr[i][2]
                turn_right(arr[i])
                tempDir *= -1
            else:
                tempFlagLeft = arr[i][6]
                tempFlagRight = arr[i][2]
                turn_left(arr[i])
                tempDir *= -1
        else:
            break

ans = 0
for i in range(len(arr)):
    if arr[i][0] == 1:
        if i == 0:
            ans += 1
        elif i == 1:
            ans += 2
        elif i == 2:
            ans += 4
        else:
            ans += 8
print(ans)

시간 복잡도(Time Complexity)와 공간 복잡도(Space Complexity)

시간 복잡도는 속도에 관한 것이며 공간 복잡도는 메모리 사용량에 관한 것이다. 시간 복잡도는 연산 횟수로 구한다.


데이터의 개수가 n이하는 알고리즘 A가 유리하고 n이상은 알고리즘 B가 유리한것을 확인할 수 있다.
따라서 상황에 맞게 적절한 알고리즘을 택해야 한다. 다음의 코드를 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
 
int LSearch(int arr[], int len, int target) { // Linear Search 함수
    for (int i = 0; i < len; i++) {
        if (arr[i] == target) // 찾으면 해당 위치의 인덱스 반환
            return i;
    }
    return -1;
}
int main(void) {
    int arr[] = { 25319 };
    int idx;
 
    idx = LSearch(arr, sizeof(arr) / sizeof(int), 9);
    if (idx == -1
        printf("탐색 실패\n");
    else 
        printf("타겟 인덱스: %d \n", idx);
 
    idx = LSearch(arr, sizeof(arr) / sizeof(int), 6);
    if (idx == -1)
        printf("탐색 실패\n");
    else
        printf("타겟 인덱스: %d \n", idx);
 
    return 0;
}
cs

이 경우 최악의 시간복잡도는 O(N)이다. 최선, 평균, 최악이 있지만 최악을 기준으로 잡는다.
이번에는 이진 탐색(Binary Search)알고리즘을 보자. 순차 탐색에 비해 좋은 성능을 내지만 정렬이 되어있어야 한다는 제약 조건이 존재한다.

이진탐색 (Binary Search)

이진탐색을 먼저 그림으로 나타내면 다음과 같다.

3을 찾기 위해 반씩 줄여나가며 총 3회 탐색하는 모습


이진탐색의 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
 
int BSearch(int arr[], int len, int target) {
    int first = 0;
    int last = len - 1;
    int mid;
 
    while (first <= last) { // fist와 last가 뒤집어지면 종료
        mid = (first + last) / 2;
        if (arr[mid] == target)
            return mid;
        else if (arr[mid] > target) // 중간 값이 target보다 큰 경우
            last = mid - 1// mid 좌측에서 탐색 진행
        else // 중간 값이 target보다 작은 경우
            first = mid + 1;
    }
    return -1// 탐색하지 못한 경우
}
 
int main(void) {
    int arr[] = { 1357911 };
    int idx;
 
    idx = BSearch(arr, sizeof(arr) / sizeof(int), 7);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟 인덱스 위치: %d \n", idx);
 
    idx = BSearch(arr, sizeof(arr) / sizeof(int), 8);
    if (idx == -1)
        printf("탐색 실패 \n");
    else
        printf("타겟 인덱스 위치: %d \n", idx);
 
    return 0;
}
cs

이 경우 최악의 시간 복잡도는 O(logN)이다.

각 빅 - 오 표기법들의 성능 비교

각 빅-오 표기법들의 성능은 다음과 같다.


순서대로 O(1) < O(logN) < O(N) < O(NlogN) < O(𝑁^2) < O(2^N) < O(N!) 이다.

'자료구조' 카테고리의 다른 글

5. 스택  (0) 2022.01.10
4. 연결 리스트  (0) 2021.07.19
3. 배열 기반 리스트  (0) 2021.07.19
2. 재귀 (Recursion)  (0) 2021.06.27
개념

사이클 판별 알고리즘은 앞선 포스팅인 서로소 집합 알고리즘을 이용한 알고리즘입니다. 그림과 같이 노드 1, 2, 3이 있고 각각의 연결이 서로를 가리키게 되어있다고 가정합시다.

이 경우 앞선 서로소 집합 알고리즘의 find_parent를 이용하면 노드1의 부모는 노드 1, 노드 2의 부모는 노드 1, 노드 3의 부모는 노드 1로 모든 노드의 부모가 각각 노드 1 임을 확인할 수 있습니다. 따라서 사이클이 발생을 했다는 것을 알 수 있습니다. 그러므로 사이클 판별 알고리즘의 경우 두 노드를 확인한 후 부모 노드가 다르다면 union_parent 함수를 수행해주고 부모 노드가 같다면 사이클이 존재한다는 것을 출력한 뒤 함수를 종료하면 되는 것입니다. 소스코드는 다음과 같습니다.


소스코드

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def find_parent(parent, x):
  if parent[x] != x:
    parent[x] = find_parent(parent, parent[x])
  return parent[x]
 
def union_parent(parent, a, b):
  a = find_parent(parent, a)
  b = find_parent(parent, b)
  if a < b:
    parent[b] = a
  else:
    parent[a] = b
  
v, e = map(int, input().split())
parent = [0* (1 + v)
 
for i in range(11 + v):
  parent[i] = i
 
cycle = False
 
for i in range(e):
  a, b = map(int, input().split())
  if find_parent(parent, a) == find_parent(parent, b):
    cycle = True
    break
  else:
    union_parent(parent, a, b)
 
if cycle:
  print("사이클 발생")
else:
  print("사이클 X")
  
cs
개념

편집거리 알고리즘이란 두 문자열이 주어졌을 때 문자열을 비교하는 알고리즘입니다. 여기서 비교한다는 것은 삽입, 삭제, 교체 즉 3가지의 액션이 주어졌을 때 두 문자열이 같게 하기 위해 취할 수 있는 액션의 최솟값을 구하는 알고리즘이라는 것입니다. 예를 들어서 str1 = python, str2 = pyytthon 라 가정하고 문제를 해결해보겠습니다. 이 알고리즘은 다이내믹 프로그래밍 즉 dp의 일종이기 때문에 dp 테이블의 형태로 이차원 리스트를 구현하면 쉽게 해결할 수 있습니다. 

다음의 표를 봅시다.

  None p y y t h o n
None 0 1 2 3 4 5 6 7
p 1 0 1          
y 2              
t 3              
h 4              
o 5              
n 6              

먼저 str1을 행으로 str2를 열로 넣고 각 행과 열에 숫자를 넣은 상태입니다. 여기서 만약 p, p를 비교하는 경우 즉 2행 2열의 경우는 두 문자가 같기 때문에 좌측 위의 0이 그대로 2행 2열에 대입됩니다.

그러나 p, y와 같은 두 문자열이 다른 경우 즉 2행 3열의 경우 좌측, 좌측 위, 위 의 3가지 경우 중 최솟값에 1을 더한 값이 대입됩니다. 여기서는 최솟값이 2행 2열 즉 0 이기 때문에 0에 1을 더한 1이 대입됩니다. 그 과정을 전부 다 하면 다음과 같습니다.

  None p y y t h o n
None 0 1 2 3 4 5 6 7
p 1 0 1 2 3 4 5 6
y 2 1 0 1 2 3 4 5
t 3 2 1 1 1 2 3 4
h 4 3 2 2 2 1 2 3
o 5 4 3 3 3 2 1 2
n 6 5 4 4 4 3 2 1

이 표의 최우 측 하단의 값이 두 문자열을 비교한 후 서로 같게 하는 최소의 액션의 수입니다. python에 y뒤에 혹은 y앞에 y를 삽입하는 1번의 과정으로 pyyhton을 만들 수 있기 때문입니다.


소스코드

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
str1 = "sunday"
str2 = "saturday"
 
= len(str1)
= len(str2)
dp = [[0* (1+m) for _ in range(1+n)]
for i in range(1, n+1):
  dp[i][0= i
for j in range(1, m+1):
  dp[0][j] = j
for i in range(1, n+1):
  for j in range(1, m+1):
    if str1[i-1== str2[j-1]:
      dp[i][j] = dp[i-1][j-1]
    else:
      dp[i][j] = min(dp[i][j-1], dp[i-1][j-1], dp[i-1][j]) + 1
 
print(dp[n][m])
cs

 

 

 

DFS란?

DFS 즉 Depth-Frist-Search는 영어를 해석한 그대로 깊이를 우선으로 탐색하는 즉 깊이 우선 탐색입니다.

깊이를 우선 탐색한다는 것은 그래프에서 한 노드에서 연결된 다른 노드들 중 한 방향의 노드에 대해서 계속 파고 들어간다는 뜻 입니다. 그림 1을 보시면 이해가 더욱 쉽습니다. 

 

그림 1

 

그림 1에서 노드 1을 시작점으로 DFS를 수행해보겠습니다. (위 예시에서 설명을 용이하게 하기 위해 낮은 숫자부터 방문한다고 가정하겠습니다.)

1번을 탐색하고 난 뒤 갈 수 있는곳은 2, 3, 7 이 있지만 낮은 숫자인 2를 탐색합니다.(탐색:  1, 2)

2번을 탐색하고 난 뒤 갈 수 있는곳은 9 가 있습니다. 9를 탐색합니다. (탐색: 1, 2, 9)

9번을 탐색하고 난 뒤 갈 수 있는곳은 3, 4가 있지만 낮은 숫자인 3을 탐색합니다. (탐색: 1, 2, 9, 3)

3번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 9로 돌아온 후 4를 탐색합니다. (탐색: 1, 2, 9, 3, 4)

4번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 1로 돌아온 후 7을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7)

7번을 탐색하고 난 뒤 갈 수 있는곳이 6, 8, 이 있지만 낮은 숫자인 6을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6)

6번을 탐색하고 난 뒤 갈 수 있는곳이 없으므로 다시 7로 돌아온 후 8을 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6, 8)

8번을 탐색하고 난 뒤 갈 수 있는곳은 5가 있습니다. 5를 탐색합니다. (탐색: 1, 2, 9, 3, 4, 7, 6, 8, 5)

모든 곳을 탐색하였으므로 탐색을 종료합니다.

탐색이 되는 순서는 위와 같이 1, 2, 9, 3, 4, 7, 6, 8, 5입니다. DFS는 구현을 스택을 이용하여 합니다만 제가 공부를 했던 나동빈 님의 이것이 취업을 위한 코딩 테스트다 with Python에서는 재귀의 형태로 구현을 하였고 저도 해당 코드를 인용하기 때문에 재귀를 이용하여 구현하겠습니다.

 


소스코드

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def dfs(graph, visited, start):
  visited[start] = True # 방문처리
  print(start, end=" "
  for i in graph[start]: # start와 연결되어 있는 노드 탐색 중
    if not visited[i]: # 방문하지 않은 노드가 있다면
      dfs(graph, visited, i) # 재귀적으로 호출
 
graph = [
  [],
  [237],
  [19],
  [19],
  [9],
  [8],
  [7],
  [168],
  [57],
  [234]
]
visited = [False* 10
 
dfs(graph, visited, 1)
cs

 

 

15686번: 치킨 배달

크기가 N×N인 도시가 있다. 도시는 1×1크기의 칸으로 나누어져 있다. 도시의 각 칸은 빈 칸, 치킨집, 집 중 하나이다. 도시의 칸은 (r, c)와 같은 형태로 나타내고, r행 c열 또는 위에서부터 r번째 칸

www.acmicpc.net

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

문제


문제 해결을 위한 과정

이 문제의 경우 가장 중요했던 것은 조합 즉 combination을 이용하는 것이었습니다. 만일 a, b, c, d, e라는 5개의 치킨집이 있을 때 3개를 뽑는 경우라면 a, b, c와 a,c,b와 b,a,c와 b,c,a 등등 이렇게 뽑은 것이라면 같은 경우입니다. 즉 순서가 중요하지 않은 조합을 이용하여 해결하는 것입니다. 파이썬에서 조합을 사용하기 위해서는 다음의 코드를 추가합니다.

from itertools import combinations

이 방법을 통해 치킨집들중 입력받은 M개를 고른 후 각각의 집마다 치킨 거리를 구한 후 도시의 최소 치킨 거리를 구하면 됩니다.


문제 해결을 위한 팁

None

소스코드

town.append(list(map(<span

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from itertools import combinations
 
def compute(data):
  answer = 0
  for i in range(len(house)):
    a, b = house[i]
    temp = int(1e9)
    for x, y in data:
      temp = min(temp, abs(x - a) + abs(y - b))
    answer += temp
  return answer
 
N, M = map(int, input().split())
town = []
house = []
store = []
 
for i in range(N):
  town.append(list(map(int, input().split())))
  for j in range(N):
    if town[i][j] == 1:
      house.append((i, j))
    elif town[i][j] == 2:
      store.append((i, j))
 
ans = int(1e9)
for data in combinations(store, M):
  ans = min(ans, compute(data))
 
print(ans)
 
 
cs

 

 

코딩테스트 연습 - 모의고사

수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다. 수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다. 1번 수포자가 찍는

programmers.co.kr

https://programmers.co.kr/learn/courses/30/lessons/42840

문제

문제 설명

수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다. 수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다.

1번 수포자가 찍는 방식: 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...
2번 수포자가 찍는 방식: 2, 1, 2, 3, 2, 4, 2, 5, 2, 1, 2, 3, 2, 4, 2, 5, ...
3번 수포자가 찍는 방식: 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, ...

1번 문제부터 마지막 문제까지의 정답이 순서대로 들은 배열 answers가 주어졌을 때, 가장 많은 문제를 맞힌 사람이 누구인지 배열에 담아 return 하도록 solution 함수를 작성해주세요.

제한 조건

  • 시험은 최대 10,000 문제로 구성되어있습니다.
  • 문제의 정답은 1, 2, 3, 4, 5중 하나입니다.
  • 가장 높은 점수를 받은 사람이 여럿일 경우, return하는 값을 오름차순 정렬해주세요.

입출력 예

answersreturn

[1,2,3,4,5] [1]
[1,3,2,4,2] [1,2,3]

입출력 예 설명

입출력 예 #1

  • 수포자 1은 모든 문제를 맞혔습니다.
  • 수포자 2는 모든 문제를 틀렸습니다.
  • 수포자 3은 모든 문제를 틀렸습니다.

따라서 가장 문제를 많이 맞힌 사람은 수포자 1입니다.

입출력 예 #2

  • 모든 사람이 2문제씩을 맞췄습니다.

문제 해결을 위한 과정

Level 1 문제로서 쉬운 문제였습니다. 단순하게 사람을 p1, p2, p3라 한다면 시험문제가 최대 10,000개 이므로 다음과 같습니다.

 

p1의 경우 [1, 2, 3, 4, 5] * 2000 

p2의 경우 [2, 1, 2, 3, 2, 4, 2, 5] * 2000 

p3의 경우 [3, 3, 1, 1, 2, 2, 4, 4, 5, 5] * 1000

여기서 p2의 경우 반복되는 숫자가 8 이므로 원래는 대략 1300을 곱해줘야 하지만 대략적인 값으로 2000번 반복을 시켰습니다. 그 후 입력으로 들어오는 answers 리스트와 비교하며 찍은 답이 정답이라면 각각의 카운트를 증가시켜 줍니다. 


문제 해결을 위한 팁

저의 경우는 정답자가 여러 명인 경우 오름차순으로 정렬을 하기 위해 임시 리스트 temp = []를 선언하였습니다. 그 후 각 사람별 맞힌 정답의 수와 각 사람(몇 번째 사람인지)에 대한 정보를 묶어서 temp리스트에 append 해주었습니다.

이 방법을 통해 문제를 많이 맞힌 사람별로 정렬이 가능하고 그 사람이 몇 번째 사람인지 조회도 가능합니다.


소스코드

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def solution(answers):
    answer = []
    people_1 = [12345* 2000
    people_2 = [21232425* 2000
    people_3 = [3311224455* 1000
    num_of_1 = 0; num_of_2 = 0; num_of_3 = 0
    
    for i in range(len(answers)):
        if answers[i] == people_1[i]:
            num_of_1 += 1
        if answers[i] == people_2[i]:
            num_of_2 += 1
        if answers[i] == people_3[i]:
            num_of_3 += 1
    
    temp = [(num_of_1,1), (num_of_2,2), (num_of_3,3)] 
    temp.sort(reverse = True)
    temp.append((-1-1)) # 마지막 원소를 비교하기 위해 의미없는 수 입력 index out of range를 방지 하기 위함
    
    for i in range(3):
        if temp[i][0!= temp[i+1][0]: # 내림차순을 한 결과가 다음 원소랑 다르다면 앞 사람이 무조건 많이 맞춘 사람이므로 앞 사람만 append 해줌
            answer.append(temp[i][1])
            answer.sort()
            return answer
        elif temp[i][0== temp[i+1][0]: 
            answer.append(temp[i][1])
    answer.sort()
    return answer
cs

+ Recent posts