BFS

def bfs(graph, visited, start):
    q = deque([start])
    visited[start] = True

    while q:
        cur = q.popleft()

        for i in range(n):
            if visited[cur]:
                continue

            visit[i] = True
            q.append(i)

DFS

def dfs(graph, visited, start):
    visited[start] = True

    for nxt in graph[start]:
        if not visited[nxt]:
            dfs(nxt)

백트래킹, 정규표현식

프로그래머스: 2019 카카오 개발자 겨울 인턴십 - 불량 사용자
import re
from copy import deepcopy

answer = []
banned_id_matches = []


def solution(user_ids, banned_ids):
    global answer, banned_id_matches

    set_banned_id_matches(user_ids, banned_ids)
    search(set(), 0)

    return len(answer)


def set_banned_id_matches(user_ids, banned_ids):
    for i in range(len(banned_ids)):
        p = re.compile("^" + banned_ids[i].replace("*", ".") + "$")
        banned_id_matches.append({user for user in user_ids if p.match(user)})


def search(visited, idx):
    if idx == len(banned_id_matches):
        if visited not in answer:
            answer.append(deepcopy(visited))
        return

    for user in banned_id_matches[idx]:
        if user in visited:
            continue

        visited.add(user)
        search(visited, idx + 1)
        visited.remove(user)

정규표현식

프로그래머스: 2019 KAKAO BLIND RECRUITMENT - 매칭 점수
import re
from collections import defaultdict

link_relations = defaultdict(list)
link_dict = {}


def solution(word, pages):
    result = []

    my_link_re = re.compile('<meta(.+?)/>', re.MULTILINE)
    links_re = re.compile('<a(.+?)>', re.MULTILINE)
    # words_re = re.compile('[^a-zA-Z]'+word+'[^a-zA-Z]', re.I)

    for idx, page in enumerate(pages):
        my_link = getUrl(my_link_re.search(page).group())
        links = [getUrl(link) for link in links_re.findall(page)]
        score = re.sub('[^a-z]+', '.', page.lower()).split('.').count(word.lower())

        link_dict[my_link] = { 'index': idx, 'score': score, 'link_count': len(links) }

        for link in links:
            link_relations[link].append(my_link)

    for my_link in link_dict.keys():
        idx = link_dict[my_link]['index']
        score = link_dict[my_link]['score']
        matching_score = score + getLinkScore(my_link)

        result.append((matching_score, idx))

    return sorted(result, key = lambda x: (-x[0], x[1]))[0][1]


def getUrl(str):
    return str.split('"')[-2]


def getLinkScore(my_link):
    link_score = 0

    for linked_link in link_relations[my_link]:
        if link_dict[linked_link]['link_count'] == 0:
            continue
        link_score += link_dict[linked_link]['score'] / link_dict[linked_link]['link_count']

    return link_score

이진탐색

def binary_search(arr, target, start, end):
    while start <= end:
        mid = (start + end) // 2

        if arr[mid] == target:
            return mid

        if arr[mid] < target:
            start = mid + 1
        else:
            end = mid - 1

    return None

파라메트릭 서치

프로그래머스: 입국심사
def solution(n, times):
    answer = 0
    start, end, mid = 1, times[-1] * n, 0

    while start < end:
        mid = (start + end) // 2

        total = 0
        for time in times:
            total += mid // time

        if total < n:
            start = mid + 1
        else:
            end = mid

    answer = start

    return answer
프로그래머스: 2019 카카오 개발자 겨울 인턴십 - 징검다리 건너기
def solution(stones, k):
    left, right = 1, 200000001
    # 마지막 사람이 건너기 위해 그 전에 최대로 건널 수 있는 수
    result = 0

    while left <= right:
        mid = (left + right) // 2

        if isNextPeoplePossibleCross(mid, stones, k):
            left = mid + 1
            result = mid
        else:
            right = mid - 1

    # 마지막 사람까지 건너면 1을 더해줘야 함
    return result + 1


def isNextPeoplePossibleCross(num, stones, k):
    count = 0
    for i in range(len(stones)):
        if stones[i] - num <= 0:
            count += 1
        else:
            count = 0

        if count >= k:
            return False

    return True

다익스트라 (a에서 b까지 최단경로)

def solution(n, costs):
    graph = [[] for _ in range(n)]

    for a, b, cost in range(costs):
        graph[a][b] = cost

    dists = [math.inf] * n

    dijkstra(graph, dists, 0)


def dijkstra(graph, dists, start):
    pq = []
    heappush(pq, (0, start))
    dists[start] = 0

    while pq:
        nowDist, now = heappop(pq)

        if dists[now] < nowDist:
            continue

        for nxt, nxtCost in graph[now]:
            dist = nowDist + nxtCost

            if dist < dists[nxt]:
                dists[nxt] = dist
                heappush(pq, (dist, nxt))

플로이드 워셜 (노드들에서 노드들까지 모든 최단경로)

import math

# n은 노드 수
def solution(n, costs):
    graph = [[math.inf] * n for _ in range(n)]

    for a in range(n):
        for b in range(n):
            if a == b:
                graph[a][b] = 0

    for a, b, cost in range(costs):
        graph[a][b] = cost

    for k in range(n):
        for a in range(n):
            for b in range(n):
                if a == k or b == k or a == b:
                    continue
                graph[a][b] = min(graph[a][b], graph[a][k] + graph[k][b])

MST - 프림

from heapq import heappush, heappop

def solution(n, costs):
    graph = [[] for _ in range(101)]

    for a, b, cost in costs:
        graph[a].append((b, cost))
        graph[b].append((a, cost))

    return getMinCost(graph)


def getMinCost(graph):
    visited = set([0])
    pq = []
    result = 0

    for nxtNode, nxtCost in graph[0]:
        heappush(pq, (nxtCost, nxtNode))

    while pq:
        curCost, curNode = heappop(pq)

        if curNode in visited:
            continue

        visited.add(curNode)
        result += curCost

        for nxtNode, nxtCost in graph[curNode]:
            heappush(pq, (nxtCost, nxtNode))

    return result

서로소 집합, Union Find

Find
# 자식 노드들이 모두 최고 조상 부모를 가리킬 경우 (경로 압축)
def find(node_num):
    if parent[node_num] != node_num:
        parent[node_num] = find(parent[node_num])

    return parent[node_num]

# 자식 노드들이 기존 부모를 기억해야되는 경우 (탐색 비효율)
def find(node_num):
    if parent[node_num] != node_num:
        return find(parent[node_num])

    return node_num
Union
# 단순히 부모 노드 숫자가 작은 쪽으로 합치기
def union(node1_num, node2_num):
    node1_root = find(node1_num)
    node2_root = find(node2_num)

    if node1_root == node2_root:
        return

    if rank[node1_root] < rank[node2_root]:
        parent[node2_root] = node1_root
    else:
        parent[node1_root] = node2_root

# 랭크값이 높은 부모 쪽으로 합치기
def union(node1_num, node2_num):
    node1_root = find(node1_num)
    node2_root = find(node2_num)

    if node1_root != node2_root:
        if rank[node1_root] > rank[node2_root]:
            parent[node2_root] = node1_root
        else:
            parent[node1_root] = node2_root

            if rank[node1_root] == rank[node2_root]:
                rank[node2_root] += 1
서로소 집합 사이클 판별
cycle = False

for i in range(n):
    a, b = map(int, input().split())

    if find(parents, a) == find(parents, b):
        cycle = True
        break

    union(parents, a, b)

MST - 크루스칼

parent = {}
rank = {}


def solution(n, costs):
    return kruskal(n, costs)


def kruskal(n, costs):
    for v in range(n):
        make_set(v)

    totalCost = 0
    costs.sort(key=lambda x: x[2])

    for node1_num, node2_num, cost in costs:
        if find(node1_num) != find(node2_num):
            union(node1_num, node2_num)
            totalCost += cost

    return totalCost


def make_set(node_num):
    parent[node_num] = node_num
    # 랭크 필요할 때만
    rank[node_num] = 0

위상정렬

def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
    g = defaultdict(list)
    indegrees = [0 for _ in range(numCourses)]

    for a, b in prerequisites:
        g[a].append(b)
        indegrees[b] += 1

    q = deque()
    for node in range(numCourses):
        if indegrees[node] == 0:
            q.append(node)

    ret = []
    while q:
        cur = q.popleft()
        ret.append(cur)
        for nxt_node in g[cur]:
            indegrees[nxt_node] -= 1
            if indegrees[nxt_node] == 0:
                q.append(nxt_node)

    return ret[::-1] if len(ret) == numCourses else []

트라이 자료구조

프로그래머스: 2020 KAKAO BLIND RECRUITMENT - 가사 검색
class Node:
    def __init__(self, data):
        self.data = data
        self.count = 0
        self.child = {}


class Trie:
    def __init__(self):
        self.head = Node(None)

    def insert(self, string):
        cur = self.head
        cur.count += 1

        for c in string:
            if c not in cur.child:
                cur.child[c] = Node(c)
            cur = cur.child[c]
            cur.count += 1

    def count(self, prefix):
        cur = self.head

        for c in prefix:
            if c not in cur.child:
                return 0
            cur = cur.child[c]

        return cur.count


def solution(words, queries):
    answer = []

    tries = create_trie(words)
    reversed_tries = create_trie(words, True)

    for query in queries:
        answer.append(count_matched_word(tries, reversed_tries, query))

    return answer


def create_trie(words, is_reversed=False):
    trie_dic = {i: Trie() for i in range(1, 10001)}

    for word in words:
        if is_reversed:
            word = word[::-1]
        trie_dic[len(word)].insert(word)

    return trie_dic


def count_matched_word(tries, reversed_tries, query):
    no_mark_query = query.replace('?', '')

    if query[0] == '?':
        return reversed_tries[len(query)].count(no_mark_query[::-1])
    else:
        return tries[len(query)].count(no_mark_query)

KMP

def kmp(s, p):
    res = []
    pi = get_pi(p)
    n, m, j = len(s), len(p), 0

    for i in range(n):
        while j > 0 and s[i] != p[j]:
            j = pi[j-1]

        if s[i] == p[j]:
            if j == m-1:
                res.append(i-m+1)
                j = pi[j]
            else:
                j += 1

    return res


def get_pi(p):
    m, j = len(p), 0
    pi = [0 for _ in range(m)]

    for i in range(1, m):
        while j > 0 and p[i] != p[j]:
            j = pi[j-1]

        if p[i] == p[j]:
            j += 1
            pi[i] = j

    return pi

LIS

LIS 길이만 구하기 (N^2)
N = 6
arr = [2, 3, 1, 5, 4, 6]
dp = [1] * 6

for i in range(1, N):
    for j in range(i):
        if arr[i] > arr[j]:
            dp[i] = max(dp[i], dp[j] + 1)
LIS 길이만 구하기 (N*logN)
from bisect import bisect_left

arr = [1, 3, 4, 2, 1]
table = []
for num in arr:
    if table[-1] < num:
        table.append(num)
    else:
        idx = bisect_left(table, num)
        table[idx] = num
LIS 역추적 (n^2)
N = 6
arr = [2, 3, 1, 5, 4, 6]
dp = [1] * 6

for i in range(1, N):
    for j in range(i):
        if arr[i] > arr[j]:
            dp[i] = max(dp[i], dp[j] + 1)

order = max(dp)
path = []

for i in range(N - 1, -1, -1):
    if dp[i] == order:
        path.append(arr[i])
        order -= 1

path.reverse()

LCS

def lcs(a, b):
    m = len(a)
    n = len(b) 
    dp = [[None] * (n + 1) for _ in range(m + 1)]

    for i in range(m + 1):
        for j in range(n + 1):
            if i == 0 or j == 0:
                dp[i][j] = 0

            elif X[i - 1] == Y[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1

            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

펜윅 트리 (부분합)

class FenwickTree(object):
    def __init__(self, nums):
        n = len(nums)
        self.nums = [0 for _ in range(n+1)]
        self.N = [0 for _ in range(n+1)]
        for i, v in enumerate(nums):
            self.set(i+1, v)

    def _lowbit(self, a):
        return a & -a

    def set(self, i, val):
        diff = val - self.nums[i]
        self.nums[i] = val
        while i < len(self.N):
            self.N[i] += diff
            i += self._lowbit(i)

    def get(self, i):
        ret = 0
        while i > 0:
            ret += self.N[i]
            i -= self._lowbit(i)

        return ret


class Fenwick(object):
    def __init__(self, nums):
        self.bit = FenwickTree(nums)

    def update(self, i, val):
        self.bit.set(i+1, val)

    def sumRange(self, i, j):
        return self.bit.get(j+1)-self.bit.get(i)

LRU 캐시 (O(1))

from collections import OrderedDict

class LRUCache:
    def __init__(self, Capacity):
        self.size = Capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache: return -1
        val = self.cache[key]
        self.cache.move_to_end(key)
        return val

    def put(self, key, val):
        if key in self.cache: del self.cache[key]
        self.cache[key] = val
        if len(self.cache) > self.size:
            self.cache.popitem(last=False)

+ 따끈한 최근 게시물