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

def solution(skill, skill_trees):
    answer = 0

    for skill_tree in skill_trees:
        buf = []
        for each_skill in skill_tree:
            if each_skill in skill:
                buf.append(each_skill)
        if buf == list(skill[:len(buf)]):
            answer += 1

    return answer

 

skill_trees에는 여러 스킬 트리들이 저장되어 있고, 이 각각의 스킬트리들이 skill의 순서대로 놓여있는지 판단해야 한다.

 

선후 관계의 순서가 지켜져야 하니까 처음에는 위상 정렬을 떠올렸다. 스킬트리는 실제로 위상 정렬의 대표적인 예시니까 말이다. 하지만 이 문제는 정렬의 문제가 아니라 제대로 정렬되어 있는지 판단하는 문제이므로 위상 정렬과는 달리 단순한 비교로 끝날 수 있다고 생각했다.

 

A와 B가 있을 때 B가 A 순서대로 놓여있는지 확인하기 위해서는 우선 B의 원소들이 A에 있는지를 확인해야 한다.

A에 존재하는 B의 원소들을 buf 라는 새로운 리스트에 추가한 뒤, 나중에 A와 buf를 비교해서 일치하면 True다.

단, buf ⊂ A 일 수 있으므로 A는 buf의 길이까지만 비교한다. 

'알고리즘 > 문자열' 카테고리의 다른 글

문자열 내 마음대로 정렬하기 (Python)  (0) 2020.07.03

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

 

문자열 형태로 주어지는 숫자로 만들 수 있는 모든 수에서 소수의 갯수를 찾는 문제이다. 이 문제를 두 개로 나누어보자. 

1) 문자열 형태로 주어지는 숫자로 만들 수 있는 모든 수를 구하기.

2) 소수를 찾기.

 

먼저 1)번을 보자. 문자열 형태로 주어지는 숫자란, "17", "011" 등을 말한다. 만약 "17"이 주어졌다면 "1", "7"로 만들 수 있는 모든 수를 구해야 한다.

문자열 형태든 무슨 형태든 일단 헷갈리니까  1과 7을 가지고 모든 수를 구하는 방법을 생각해보자.

모든 수를 구한다는 것은 가능한 모든 경우의 수를 구한다는 것이다. 순열을 구하는 방식으로 가능한 모든 경우의 수를 구할 수 있겠다. (조합이 아님)

그리고 매개변수가 문자열 형태로 주어지므로, "17"을 "1"과 "7"로 만들기 위해서 간단히 문자열을 리스트로 감싸주자. 

ex) list("17") --> ["1", "7"]

 

1)번 문제는 for문을 직접 돌며 재귀로 구할 수도 있고, itertools의 permutation을 이용해서 풀 수도 있다.

itertools 방식이 훨씬 깔끔하긴 하다.

 

2)번 소수를 찾는 문제에 대해 얘기해보자. 만약 작은 수부터 차례대로 소수를 구하는 문제였다면 소수 판별을 위해서 더 작은 소수로만 나눠보려고 했을 것이다.

하지만 이 문제에서는 "17"처럼 7보다 작은 소수인 2, 3, 5가 등장하지 않고 7이 제일 먼저 등장할 수도 있다.

그래서 자기보다 작은 소수로 나눠보는 방식으로 판별은 불가능하고(아니 복잡해지고), 1보다는 크고, 자신의 제곱근보다 같거나 작은 수로 나눠봐야 한다.

 

 

itertools를 사용하지 않은 코드

def dfs(n_list, checked, true_cnt, primes, selected):
    if true_cnt > 0:
        n = int("".join(selected))
        if is_prime(n):
            primes.add(n)
        if true_cnt == len(n_list):
            return

    for idx, ch in enumerate(checked):
        if not checked[idx]:
            checked[idx] = True
            selected.append(n_list[idx])
            dfs(n_list, checked, true_cnt+1, primes, selected)
            checked[idx] = False
            selected.pop()

    return primes


def solution(numbers):
    n_list = list(numbers)  # numbers의 길이를 알아내기 위해 리스트로 바꾼다.
    checked = [False for _ in n_list]
    return len(dfs(n_list, checked, 0, set(), []))

 

 

itertools를 사용한 코드

from itertools import permutations


def is_prime(n):
    prime = True
    if n < 2:
        prime = False
    for i in range(2, int(n ** 0.5)+1):  # n의 0.5제곱 = 제곱근
        if n % i == 0:
            prime = False
    return prime


def solution(numbers):
    answer = set()
    for i in range(1, len(numbers)+1):
        for p in permutations(numbers, i):
            n = int("".join(p))
            if is_prime(n):
                answer.add(n)
    return len(answer)

 

 

'알고리즘 > 브루트포스' 카테고리의 다른 글

16637. 괄호 추가하기 (2)  (0) 2020.05.12
16637번. 괄호 추가하기  (0) 2020.05.11
12100번. 2048 (Easy)  (0) 2020.05.04
17609번. 회문  (0) 2020.04.19
12100. 2048 (Easy)  (0) 2020.01.07

programmers.co.kr/learn/courses/30/lessons/42576

from collections import Counter


def solution(participant, completion):
    return list((Counter(participant) - Counter(completion)).keys())[0]

 

Level 1 문제라 그런지 코드가 매우 짧다. 파이썬으로 이 문제를 푸는 방법은 두 가지가 있다. 완전탐색, 그리고 딕셔너리끼리의 뺄셈.

 

완전탐색은 for문 안에 if ~ in 문을 넣어서 풀면 된다.

for c in completion:
    if c in participant:
        participant.remove(c)
return participant[0]

participant는 참가자 전원의 리스트이고 completion은 완주한 선수들의 리스트이다. participant보다 한 명이 적다.

졸려서 그랬는지 처음에는 participant를 순회하려고 했다. 그렇게 하면 participant를 순회하는 도중에 participant에서 remove를 하게 되어 에러가 발생한다. 리스트를 순회하는 도중에 값의 변경이 일어나면 안되기 때문이다.

대신에, completion을 순회하면서 그 값이 participant에 존재하면 participant에서 remove해주는 방식으로 해야 한다.

즉, 완주한 선수들의 이름을 하나씩 부르면서 참가자 전원 리스트에서 하나씩 소거해 나가는 것이다.

완주한 선수들의 이름을 모두 다 불렀는데도 참가자 리스트에 남아있는 선수가 바로 완주하지 못한 선수가 된다.

아무튼 설명이 길었지만 결론은 완전탐색 방식으로 하면 시간이 O(n^2)이 걸리게 되어 시간초과가 난다.

 

그럼 어떻게 해야 할까? 파이썬에서는 collections 모듈에서 Counter 함수를 제공한다.

Counter 함수에는 여러가지 사용법이 있는데, 매개변수로 리스트를 넣어주면 {값: 갯수}의 형태의 딕셔너리를 반환한다.

ex) collections.Counter(["leo", "kiki", "eden"]) ----> {"leo": 1, "kiki": 1, "eden": 1}

 

중요한 점이 여기서 등장한다. 원래 파이썬에서 set과는 달리 딕셔너리끼리 뺄셈을 지원하지 않는다.

그런데 Counter로 반환된 딕셔너리 모양의 오브젝트끼리는 더하기, 빼기, 차집합, 합집합이 가능하다.

따라서, 참가자 전원 Counter에서 완주한 선수 Counter를 빼 주면 이름이 중복되는 선수까지 다 계산해서 최종적으로 완주하지 못한 선수가 리턴된다.

맨 처음에 올린 코드처럼!

 

Counter 개념을 익혀놓자.

https://programmers.co.kr/learn/courses/30/lessons/12915?language=python3

def solution(strings, n):
    return sorted(sorted(strings), key=lambda x: x[n])

 

굉장히 짧은 코드이지만 후에 lambda에 대해 이해하기 좋을 것 같아 포스팅해 둔다.

 

map(lambda x: x+2, range(5)) → range(5)에서 하나씩 가져와서 x+2를 한다.

 

sorted(strings, key=lambda x: x[n]) → strings에서 하나씩(x) 가져와서 정렬기준인 key로 삼는데, 그냥 x가 아니라 x[n]을 key로 삼는다.

'알고리즘 > 문자열' 카테고리의 다른 글

스킬트리 (Python)  (0) 2020.07.09

http://boj.kr/1167 

 

1167번: 트리의 지름

트리가 입력으로 주어진다. 먼저 첫 번째 줄에서는 트리의 정점의 개수 V가 주어지고 (2≤V≤100,000)둘째 줄부터 V개의 줄에 걸쳐 간선의 정보가 다음과 같이 주어진다. (정점 번호는 1부터 V까지 ��

www.acmicpc.net

 

맨 처음에는 인접 행렬을 통해서 완전 탐색을 하려고 했지만 메모리 초과가 났다.

우선 인접 행렬을 쓰면 메모리 초과가 난다. 인접 리스트를 써야 한다.

간선에 가중치가 존재하기 때문에 adj[시작노드] = [도착노드, 노드 간 거리] 이런 식으로 인접 리스트를 구성해주었다.

 

그리고 완전 탐색을 하면 아마 시간 초과가 날 것이다. 여기서는 특정 알고리즘(?)이 필요한데,

트리에서 아무 노드나 잡아서 그 노드에서 가장 먼 노드를 구한 후, 그 노드에서 또 가장 먼 노드를 구하면 그게 트리의 지름이 된다는 것이다.

생각을 해 보면 아무 노드나 잡은 후 가장 먼 노드를 구하면 루트나 리프 노드일 것이다. 거기서부터 가장 먼 노드를 구하면 그 사이 거리가 트리에서 가장 긴 거리가 될 것이다.

그러니까...DFS를 두 번 도는 이유는 첫번째는 루트나 리프, 어쨌든 끝점을 구하기 위해서라고 해도 될 것 같다. 두번째는 거기서부터 제일 먼 점을 구하는 거고.

 

일단 트리의 지름을 구하는 알고리즘은 이렇고, 한 노드에서 가장 먼 노드를 구하는 것도 은근 일이다.

나는 최단경로찾기 할 때처럼 max_dist 를 전역변수로 두고, DFS로 계속해서 자식 노드를 찾아가되 리프 노드를 발견하면 거리를 비교해 max_dist를 갱신하는 방식으로 풀었다.

 

import sys
sys.setrecursionlimit(10 ** 9)
input = sys.stdin.readline


def dfs(parent, dist, visited):
    global max_dist
    visited[parent] = 1
    leaf = True
    for child in adj[parent]:  # child = [노드번호, 거리]
        if not visited[child[0]]:
            leaf = False
            dfs(child[0], dist+child[1], visited)
    if leaf:  # leaf or root
        if max_dist[1] < dist:
            max_dist = [parent, dist]


n = int(input())

adj = {i: [] for i in range(1, n+1)}  # 인접 리스트
for _ in range(n):  # 입력 받기
    tmp = list(map(int, input().split()))
    start = tmp[0]
    endpoint = 1
    while tmp[endpoint] != -1:
        adj[start].append([tmp[endpoint], tmp[endpoint+1]])  # adj[시작점] = [[도착점, 거리], ...]
        endpoint += 2

max_dist = [-1, float('-inf')]  # [노드 번호, 거리]
visited = [0 for _ in range(n+1)]
dfs(1, 0, visited)  # 아무 점에서부터 제일 먼 점을 찾는다. 여기서는 1을 선택

max_dist[1] = float('-inf')  # 위에서 구한 점으로부터 최대 거리를 구하기 위해 값 초기화
visited = [0 for _ in range(n+1)]
dfs(max_dist[0], 0, visited)  # 아까 아무 점에서 제일 먼 점을 구했으면, 그 점에서 제일 먼 점을 또 구한다

print(max_dist[1])

 

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

11725번. 트리의 부모 찾기 (Python)  (0) 2020.05.23
programmers. 네트워크 (python)  (0) 2020.05.18
17142번. 연구소 3  (0) 2020.04.26
15971번. 두 로봇  (0) 2020.04.21
14501번. 퇴사  (0) 2020.04.17

http://boj.kr/11725 

 

11725번: 트리의 부모 찾기

루트 없는 트리가 주어진다. 이때, 트리의 루트를 1이라고 정했을 때, 각 노드의 부모를 구하는 프로그램을 작성하시오.

www.acmicpc.net

 

우선 주어진 입력을 인접 딕셔너리로 바꾼다. 주어진 입력에 방향이 없으므로 양쪽 다 인접 딕셔너리에 추가해야 한다.

그런 다음 루트인 1에서부터 find_parent 를 시작한다.

find_parent에서는 1의 자식을 순회하면서 그 자식의 부모가 1이라고 표시한다.

 

첫번째 테스트케이스에서 1의 자식은 4, 6이다. line 6에서 4와 6을 차례로 순회하게 되는데, 

line 8에서 각각 parent[4] = 1, parent[6] = 1 이라고 표시하게 되서 부모를 저장할 수 있게 된다.

그리고 4와 6을 다시 부모 삼아서 find_parent를 호출한다. 더 이상 자식이 없을 때까지 호출된다.

 

1의 자식이 4와 6이라고 했지만 주어진 입력을 인접 딕셔너리로 바꿀 때 양방향으로 추가해줬으므로 4와 6의 자식이 1이기도 한데, visited 배열에 방문 표시를 해줘서 사이클이 생기지 않도록 한다.

import sys


def find_parent(node, graph):
    visited[node] = True
    for child in graph[node]:
        if not visited[child]:
            parent[child] = node
            find_parent(child, graph)


def solution(lst):
    tree = {i: [] for i in range(1, n+1)}  # 인접 딕셔너리 (0은 제외)
    for a, b in lst:
        tree[a].append(b)  # 주어진 입력에 방향이 없으므로 양쪽 다 추가
        tree[b].append(a)

    visited[1] = True
    find_parent(1, tree)  # 루트=1에서부터 출발

    for i in range(2, len(parent)):
        print(parent[i])


sys.setrecursionlimit(10 ** 9)
input = sys.stdin.readline
n = int(input())
adj = [list(map(int, input().split())) for _ in range(n-1)]  # 입력
visited = [0 for _ in range(n+1)]  # 0(제외), 1~7
parent = [0 for _ in range(n+1)]
solution(adj)

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

1167번. 트리의 지름  (0) 2020.05.23
programmers. 네트워크 (python)  (0) 2020.05.18
17142번. 연구소 3  (0) 2020.04.26
15971번. 두 로봇  (0) 2020.04.21
14501번. 퇴사  (0) 2020.04.17

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

 

 

 

문제를 대충 읽고 삼성st Flood fill 문제구만! 하고 시작했더니 보기좋게 틀려버렸다.

이 문제는 어떤 그래프에서 서로 연결된 클러스터들이 몇 개인지 찾아내는 문제이다.

 

2667번. 단지 번호 붙이기 문제와는 확실히 다른 문제이다. 단지 번호 붙이기 문제는 인접 행렬이 주어지는 게 아니라 단순히 2차원 행렬이 주어지는 것이었다.

하지만 이 문제는 인접 행렬이 주어지는 문제이다. 단지번호붙이기 문제에서는 (0, 0)에서 곧바로 (0, 4)를 방문하지 못하지만 인접 행렬 문제에서는 방문할 수 있다.

인접행렬에서 (0, 4) = 1 이라는건 이런 걸 의미한다.

차이를 말하자면 2차원 배열과 1차원 배열의 차이라고 할까? 사실 이 두 문제는 너무나도 다른데 내가 헷갈린거라 설명할 말도 없다. 

단지번호붙이기에서 주어진 행렬은 각 노드의 위치를 알려준 거지만 (row, col)

이 문제에서의 행렬은 인접행렬이므로 두 노드가 연결되어있는지를 알려준 것이다. (v, next_v)

그래서 visited 배열은 1차원 배열이 되어야 한다. (각 노드에 row, col 위치가 없고 노드 이름만 존재하므로)

 

+ 인접행렬에서 자기 자신으로 향하는 것을 1로 표시한다고 했는데, 이 말은 그래프 탐색을 할 때 자기 자신은 아무 처리하지 않고 일단 방문한 뒤 쓰루한다는 것을 의미한다. 

# 네트워크
def dfs(v, adj, visited):
    for next_v in range(len(adj)):
        if not visited[next_v] and adj[v][next_v]:
            visited[next_v] = 1
            dfs(next_v, adj, visited)


def solution(n, computers):
    answer = 0
    visited = [0 for _ in range(n)]

    for v in range(n):
        if not visited[v]:
            dfs(v, computers, visited)
            answer += 1

    return answer

'알고리즘 > BFS와 DFS' 카테고리의 다른 글

1167번. 트리의 지름  (0) 2020.05.23
11725번. 트리의 부모 찾기 (Python)  (0) 2020.05.23
17142번. 연구소 3  (0) 2020.04.26
15971번. 두 로봇  (0) 2020.04.21
14501번. 퇴사  (0) 2020.04.17

http://boj.kr/16637 

 

16637번: 괄호 추가하기

첫째 줄에 수식의 길이 N(1 ≤ N ≤ 19)가 주어진다. 둘째 줄에는 수식이 주어진다. 수식에 포함된 정수는 모두 0보다 크거나 같고, 9보다 작거나 같다. 문자열은 정수로 시작하고, 연산자와 정수가

www.acmicpc.net

 

 

import sys


def _calc(n1, n2, oper):
    if oper == '+':
        return n1 + n2
    elif oper == '-':
        return n1 - n2
    elif oper == '*':
        return n1 * n2


def calc():
    global max_result
    buf = []  # 연산식을 담는데, 괄호는 미리 계산해서 담는다
    
    i = 0
    while i < len(arr):
        if i+2 < len(arr) and paren[i] == paren[i+2] == True:
            tmp = _calc(int(arr[i]), int(arr[i+2]), arr[i+1])  # 괄호 안의 식은 미리 계산
            buf.append(tmp)
            i += 3  
        else:
            if arr[i].isdigit():
                buf.append(int(arr[i]))
            else:
                buf.append(arr[i])
            i += 1

    i = 2
    ret = buf[0]
    while i < len(buf):
        ret = _calc(ret, buf[i], buf[i-1])
        i += 2
    max_result = max(max_result, ret)


def dfs(i):
    while i < len(arr) - 2:  # 마지막 괄호를 쳐볼 수 있는 숫자는 끝에서 두 번째 숫자이다
        if paren[i] == False:
            paren[i] = paren[i+2] = True  # 이번 숫자와, 다음 숫자에 괄호를 표시
            dfs(i+2)
            paren[i] = paren[i+2] = False  # 재귀 함수에서 돌아온 이후에는 괄호를 체크 해제
        i += 2
    calc()
    return


input = sys.stdin.readline
N = int(input())
arr = list(input()[:-1])  # 숫자와 연산자를 모두 한 배열에 담는다.
paren = [False for _ in range(len(arr))]  # 괄호쳤음을 표시
max_result = float('-inf')
dfs(0)
print(max_result)

 

 

'알고리즘 > 브루트포스' 카테고리의 다른 글

소수 찾기 (Python)  (0) 2020.07.04
16637번. 괄호 추가하기  (0) 2020.05.11
12100번. 2048 (Easy)  (0) 2020.05.04
17609번. 회문  (0) 2020.04.19
12100. 2048 (Easy)  (0) 2020.01.07

+ Recent posts