파이썬 코딩테스트 강좌, 최소 비용 구하기

안녕하세요! 이번 글에서는 경로 탐색 문제를 기반으로 한 “최소 비용 구하기” 알고리즘 문제를 다루어 보겠습니다. 코딩 테스트는 IT 기업에서의 취업을 위해 필수적으로 준비해야 할 요소 중 하나입니다. 문제를 해결하기 위한 전략, 알고리즘 이해, 그리고 구현 능력을 기르기 위해 이 글을 통해 단계별로 문제를 풀이해보겠습니다.

문제 설명

최소 비용을 구하는 문제는 주어진 그래프에서 출발점에서 도착점으로 가는 비용의 최솟값을 구하는 문제입니다.
아래와 같은 그래프를 예로 들어봅시다.

    1 --> 2 (비용: 4)
    1 --> 3 (비용: 2)
    2 --> 3 (비용: 1)
    2 --> 4 (비용: 5)
    3 --> 4 (비용: 8)
    

위 그래프에서 1번 노드에서 4번 노드로 가는 최소 비용을 구하시오.

입력 형식

  • 첫째 줄에 노드의 수 N과 간선의 수 M (1 ≤ N ≤ 1000, 1 ≤ M ≤ 10^4)가 주어진다.
  • 둘째 줄부터 M개의 줄에 각 간선의 정보가 “u v c” 형식으로 주어진다 (u, v는 노드 번호, c는 비용).
  • 마지막 줄에 출발 노드와 도착 노드가 주어진다.

출력 형식

출발 노드에서 목표 노드로 가는 최소 비용을 출력하시오. 경로가 없을 경우 -1을 출력하시오.

문제 접근 방법

이 문제는 그래프의 최단 경로를 구하는 전형적인 문제로, Dijkstra 알고리즘을 사용할 것입니다. Dijkstra 알고리즘은 가중치가 있는 그래프에서 최단 경로를 찾는 데 매우 효율적입니다. 다음과 같은 단계를 통해 알고리즘을 구현할 것입니다.

  1. 입력받기: 노드, 간선 정보를 입력합니다.
  2. 그래프 구조화: 입력받은 간선 정보를 기반으로 그래프를 연결 리스트 형태로 표현합니다.
  3. 우선순위 큐 초기화: 현재 노드에서 인접한 노드의 비용을 관리하기 위해 우선순위 큐를 설정합니다.
  4. Dijkstra 알고리즘 수행: 큐에서 가장 낮은 비용의 노드를 꺼내어 인접한 노드로 가는 비용을 업데이트합니다.
  5. 결과 출력: 목적지에 도달 가능한지 확인하고, 최소 비용을 출력합니다.

코드 구현

import sys
import heapq

def dijkstra(start, goal, graph, n):
    # 비용 테이블 초기화
    INF = float('inf')
    distance = [INF] * (n + 1)
    distance[start] = 0
    queue = []
    heapq.heappush(queue, (0, start))  # (비용, 노드)

    while queue:
        current_cost, current_node = heapq.heappop(queue)

        # 현재 비용이 기존의 최소 비용보다 크면 무시
        if current_cost > distance[current_node]:
            continue

        for neighbor, cost in graph[current_node]:
            new_cost = current_cost + cost

            # 인접 노드의 비용 업데이트
            if new_cost < distance[neighbor]:
                distance[neighbor] = new_cost
                heapq.heappush(queue, (new_cost, neighbor))

    return distance[goal] if distance[goal] != INF else -1

if __name__ == '__main__':
    n, m = map(int, sys.stdin.readline().strip().split())
    graph = [[] for _ in range(n + 1)]

    for _ in range(m):
        u, v, c = map(int, sys.stdin.readline().strip().split())
        graph[u].append((v, c))

    start, goal = map(int, sys.stdin.readline().strip().split())
    result = dijkstra(start, goal, graph, n)
    print(result)

코드 설명

위 코드는 Python에서 Dijkstra 알고리즘을 구현한 것입니다. 입력 파라미터로 시작 노드, 도착 노드, 그래프 구조, 총 노드의 수를 받습니다.

비용 테이블 초기화: 노드 수 만큼의 무한대를 가지는 리스트를 초기화합니다. 출발 노드의 비용만 0으로 설정합니다.

우선순위 큐 사용: Python의 힙 기능인 heapq를 활용하여 가장 낮은 비용의 노드를 처리합니다.

인접 노드 업데이트: 현재 노드와 인접한 노드를 순회하며 최적 비용으로 업데이트합니다.

실행 예시

입력
4 5
1 2 4
1 3 2
2 3 1
2 4 5
3 4 8
1 4

출력
6

이 예시에서는 1번 노드에서 4번 노드로 가는 최소 비용이 6임을 보여줍니다.

그리드 그래프에서의 최소 비용 구하기

위의 문제는 일반적인 그래프이지만, 2D 그리드에서의 최소 비용을 구하는 문제도 있습니다. 아랫부분에서는 2D 배열을 통해 최소 비용을 구하는 방식에 대해 설명하겠습니다.

문제 2: 그리드에서의 최소 비용 구하기

주어진 2D 배열에서 출발점 (0, 0)에서 도착점 (n-1, m-1)까지 이동할 때의 최소 비용을 구해야 합니다. 각 칸의 값은 이동할 때의 비용입니다. 상하좌우로만 이동할 수 있습니다.

입력 형식

3 3
1 2 3
4 1 2
1 5 3

주어진 배열은 다음과 같습니다:

1  2  3
4  1  2
1  5  3

출력 형식

출발점에서 목표점으로 가는 최소 비용을 출력하시오.

문제 접근 방법

이 문제는 다이나믹 프로그래밍을 사용하여 해결할 수 있습니다. 각 칸에서 최소 비용을 계산하여 이전 칸에서 도달할 수 있는 최소 비용을 업데이트하는 방식입니다.

코드 구현

def min_cost_in_grid(grid):
    n = len(grid)
    m = len(grid[0])
    dp = [[0] * m for _ in range(n)]
    dp[0][0] = grid[0][0]
    
    for i in range(n):
        for j in range(m):
            if i == 0 and j == 0:
                continue
            up = dp[i-1][j] if i > 0 else float('inf')
            left = dp[i][j-1] if j > 0 else float('inf')
            dp[i][j] = grid[i][j] + min(up, left)

    return dp[n-1][m-1]

if __name__ == '__main__':
    grid = [
        [1, 2, 3],
        [4, 1, 2],
        [1, 5, 3]
    ]
    result = min_cost_in_grid(grid)
    print(result)

코드 설명

위 코드는 2D 배열의 최소 비용을 구하는 방법을 보여줍니다. dp 배열을 통해 각 칸의 최소 비용을 저장하며, 왼쪽 또는 위쪽에서 오는 비용을 비교하여 현재 칸의 최소 비용을 설정합니다.

실행 예시

출력
8

이 예시에서는 (0, 0)에서 (2, 2)로 가는 최소 비용이 8임을 보여줍니다.

결론

이 글에서는 최소 비용 문제를 해결하기 위한 다양한 접근 방법과 알고리즘을 살펴보았습니다. Dijkstra 알고리즘을 사용한 그래프 문제와 다이나믹 프로그래밍을 활용한 그리드 문제를 통해 코딩 테스트 준비에 도움이 되었기를 바랍니다. 문제 해결 능력을 키우기 위해 다양한 문제에 도전해 보세요!

모든 과정에서 궁금한 점이나 추가적인 질문이 있으시면 댓글로 남겨 주세요. 감사합니다!

파이썬 코딩테스트 강좌, 최소 공통 조상

안녕하세요! 이번 강좌에서는 알고리즘 문제 중 하나인 ‘최소 공통 조상(LCA, Lowest Common Ancestor)’에 대해 알아보겠습니다. 최소 공통 조상 문제는 트리 구조에서 두 노드가 주어졌을 때, 이 두 노드의 가장 가까운 공통 조상을 찾는 문제입니다. 이 문제는 다양한 분야에서 매우 중요하며, 많은 면접에서도 자주 출제되곤 합니다.

문제 설명

주어진 이진 트리(이진 탐색 트리 포함)에서 두 노드의 값이 주어질 때, 이 두 노드의 최소 공통 조상을 찾으세요.

입력

  • 노드의 수 N (1 ≤ N ≤ 1000)
  • N개의 노드의 값
  • 두 개의 노드 값 A, B (A, B는 트리에 존재함)

출력

두 노드의 최소 공통 조상의 값을 출력합니다.

문제 이해하기

최소 공통 조상 문제는 트리를 구성하는 데이터 구조의 특성과 노드의 상관 관계를 이해하는 데 도움이 됩니다. 공통 조상이란 두 노드에서 공통적으로 올라가면서 만나는 노드를 의미합니다. 예를 들어, 트리가 다음과 같다고 가정해보겠습니다.

              3
            /   \
          5      1
         / \    / \
        6   2  0   8
           / \
          7   4
    

이 트리에서 5와 1의 최소 공통 조상은 3이며, 6과 4의 최소 공통 조상은 5입니다.

해결 전략

최소 공통 조상을 찾는 기본적인 방법은 아래와 같습니다:

  1. 트리를 순회하며 주어진 두 노드의 경로를 저장합니다.
  2. 각 경로에서 마지막으로 만나는 노드를 찾습니다.

이 방법은 직관적이고 간단하지만, 비효율적인 경우가 있습니다. 대신에 아래와 같은 좀 더 효율적인 방법을 사용할 수 있습니다.

효율적인 방법

이진 트리에서 LCA를 찾는 효율적인 방법으로는 깊이 우선 탐색(DFS)을 이용한 방법이 있습니다. 이 방법은 주어진 두 노드의 값을 가진 노드들을 재귀적으로 탐색하며, 양쪽 노드가 발견되면 그 노드를 반환합니다.

코드 구현

이제 우리는 이 알고리즘을 파이썬으로 구현해보겠습니다. 다음은 LCA를 찾기 위한 코드입니다:


class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

def lowest_common_ancestor(root, p, q):
    # 기저 조건
    if not root or root == p or root == q:
        return root

    # 왼쪽과 오른쪽에서 LCA를 재귀적으로 찾기
    left = lowest_common_ancestor(root.left, p, q)
    right = lowest_common_ancestor(root.right, p, q)

    # 각 자식 노드가 존재하는 경우
    if left and right:
        return root
    return left if left else right

# 테스트 케이스
if __name__ == "__main__":
    # 트리 구성
    root = TreeNode(3)
    root.left = TreeNode(5)
    root.right = TreeNode(1)
    root.left.left = TreeNode(6)
    root.left.right = TreeNode(2)
    root.right.left = TreeNode(0)
    root.right.right = TreeNode(8)
    root.left.right.left = TreeNode(7)
    root.left.right.right = TreeNode(4)

    # 노드 p와 q 설정
    p = root.left  # 5
    q = root.right  # 1

    # LCA 계산
    lca = lowest_common_ancestor(root, p, q)
    print(f"최소 공통 조상: {lca.val}")
    

알고리즘 분석

이 알고리즘은 트리의 각 노드를 한 번만 방문하므로, 시간 복잡도는 O(N), 공간 복잡도는 O(H)입니다. 여기서 N은 노드의 수이고, H는 트리의 높이입니다. 최악의 경우에는 H가 N과 같으므로, 전체적인 시간과 공간 복잡도는 O(N)입니다.

결론

이번 강좌에서는 최소 공통 조상 문제에 대해 알아보았습니다. 이 문제는 많은 경우의 수를 고려해야 하지만, 올바른 알고리즘을 통해 간단히 해결할 수 있습니다. 실제 문제를 해결할 때는 트리의 구조와 노드 간의 관계를 확실히 이해한 후에 접근하는 것이 중요합니다. 다음 강좌에서는 다른 알고리즘 문제를 다뤄보겠습니다. 감사합니다!

파이썬 코딩테스트 강좌, 최소 공통 조상 구하기 1

안녕하세요, 여러분! 오늘은 파이썬을 이용한 코딩 테스트에서 자주 등장하는 문제 중 하나인 “최소 공통 조상 구하기”에 대해 알아보겠습니다. 이 강좌에서는 최소 공통 조상을 의미하는 ‘Lowest Common Ancestor (LCA)’를 찾는 알고리즘에 대해 상세히 설명하며, 관련된 예제 문제 및 그 해결 과정을 함께 살펴보겠습니다.

1. 문제 정의

주어진 이진트리에서 두 노드 A와 B의 최소 공통 조상을 찾는 문제입니다. 최소 공통 조상이란 두 노드를 동시에 부모로 가지는 가장 깊은 노드를 의미합니다. 예를 들어, 다음과 같은 이진트리가 있다고 가정해봅시다.

            3
           / \
          5   1
         / \ / \
        6  2 0  8
          / \
         7   4
        

위 트리에서 노드 5와 노드 1의 최소 공통 조상은 노드 3입니다. 그러나 노드 5와 노드 4의 최소 공통 조상은 노드 5입니다.

2. 문제 요구 사항

  • 입력: 이진트리의 루트 노드와 두 노드 A, B
  • 출력: A와 B의 최소 공통 조상 노드를 반환

3. 알고리즘 접근법

최소 공통 조상을 찾기 위한 여러 접근법이 있지만, 가장 간단한 방법은 DFS(깊이 우선 탐색)를 이용하는 것입니다. DFS 알고리즘을 사용하면 각 노드를 방문하면서 A와 B를 찾고, 두 노드의 공통 조상을 추적할 수 있습니다.

3.1 DFS 탐색 방법

DFS를 사용하여 이진트리를 탐색하면서 현재 노드가 두 노드 A와 B 중 하나와 일치하는지 확인합니다. 일치하는 경우 해당 노드를 반환합니다. 그렇지 않다면 두 개의 서브트리에서 각각 A와 B를 찾습니다. 다음 단계로 이 두 서브트리의 결과를 결합하여 최소 공통 조상을 찾습니다.

3.2 구현

이제 문제를 해결하기 위한 코드를 작성해 보겠습니다.


class TreeNode:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None

def lowest_common_ancestor(root, node1, node2):
    if root is None:
        return None

    # base case: if the current root is one of the nodes
    if root == node1 or root == node2:
        return root

    # look for node1 and node2 in the left and right subtrees
    left_lca = lowest_common_ancestor(root.left, node1, node2)
    right_lca = lowest_common_ancestor(root.right, node1, node2)

    # If both left_lca and right_lca are not None, it means
    # one node is present in one subtree and the other is present in the other subtree
    if left_lca and right_lca:
        return root

    # Otherwise return the non-null value
    return left_lca if left_lca is not None else right_lca
        

4. 코드 설명

먼저, 이진 트리의 각 노드를 나타내는 TreeNode 클래스를 정의합니다. 이 클래스는 각 노드의 값과 왼쪽, 오른쪽 자식 노드를 저장합니다. 다음으로, lowest_common_ancestor 함수를 정의하여 주어진 루트 노드에서 시작하여 두 개의 노드인 node1node2의 최소 공통 조상을 찾습니다.

4.1 기본 조건

루트 노드가 None인 경우에는 None을 반환합니다. 현재 루트가 node1 또는 node2와 동일한 경우에는 해당 노드를 반환합니다.

4.2 재귀적 탐색

그 후, 왼쪽 서브트리와 오른쪽 서브트리 각각에서 재귀적으로 LCA를 찾습니다. 두 서브트리에서 모두 발견된 노드가 있을 경우 현재 노드가 최소 공통 조상입니다. 그렇지 않으면 발견된 노드를 반환합니다.

5. 테스트 케이스

함수를 테스트하기 위해 다음의 트리를 구성해 보겠습니다.

            root = TreeNode(3)
            root.left = TreeNode(5)
            root.right = TreeNode(1)
            root.left.left = TreeNode(6)
            root.left.right = TreeNode(2)
            root.right.left = TreeNode(0)
            root.right.right = TreeNode(8)
            root.left.right.left = TreeNode(7)
            root.left.right.right = TreeNode(4)
        

# Test case 1: Finding LCA of 5 and 1
lca = lowest_common_ancestor(root, root.left, root.right)  # should return TreeNode(3)

# Test case 2: Finding LCA of 5 and 4
lca = lowest_common_ancestor(root, root.left, root.left.right.right)  # should return TreeNode(5)
        

6. 결론

이번 강좌에서는 파이썬을 사용하여 이진 트리에서 최소 공통 조상을 찾는 방법을 알아보았습니다. DFS 알고리즘을 통해 문제를 해결하는 과정을 살펴보았으며, 이를 통해 이진 트리에 대한 이해를 높일 수 있었습니다. 다음 강좌에서는 보다 복잡한 이진 트리 문제를 다룰 예정이니, 많은 관심 부탁드립니다!

이 글을 통해 여러분께서 LCA 문제를 이해하고, 이를 해결할 수 있는 능력을 키우셨기를 바랍니다. 감사합니다!

파이썬 코딩테스트 강좌, 최소 공배수 구하기

안녕하세요! 이번 포스트에서는 알고리즘 문제 풀이를 통해 ‘최소 공배수(Least Common Multiple, LCM)’를 구하는 방법을 자세히 알아보겠습니다. 최소 공배수는 두 개 이상의 정수의 공통 배수 중에서 가장 작은 수를 의미합니다. 프로그래밍 면접이나 코딩 테스트에서 자주 출제되므로 이 문제를 확실히 이해하고 연습하는 것이 중요합니다.

문제 정의

주어진 두 개의 정수 A와 B에 대해, 두 숫자의 최소 공배수를 구하는 함수를 작성하세요.

입력

  • 두 개의 정수 A, B (1 ≤ A, B ≤ 100,000)

출력

  • A와 B의 최소 공배수 (LCM)

예제

입력: 
4 5

출력: 
20

문제 접근

최소 공배수를 계산하기 위해서는 최대 공약수(Greatest Common Divisor, GCD)를 활용하는 것이 효율적입니다. 최소 공배수는 다음과 같은 공식으로 구할 수 있습니다:

LCM(A, B) = (A × B) / GCD(A, B)

이 공식의 유래는 두 수의 공배수에 대한 정의와 최대 공약수의 성질에서 오기 때문입니다. 두 수의 곱을 최대 공약수로 나누면, 해당 숫자들이 공유하는 배수를 제외한 나머지 배수만 포함하게 됩니다.

파이썬에서 GCD 계산하기

파이썬에서는 기본적으로 제공되는 math 모듈을 사용할 수 있어, 최대 공약수를 쉽게 구할 수 있습니다.

문제 해결을 위한 코드 작성

이제 최소 공배수를 구하는 함수를 한 단계씩 구현해 보겠습니다.

import math

def lcm(a: int, b: int) -> int:
    return (a * b) // math.gcd(a, b)

# 함수 테스트
a, b = map(int, input("두 개의 정수를 입력하세요: ").split())
print(f"{a}와 {b}의 최소 공배수는 {lcm(a, b)}입니다.")

코드 설명

  • 우선 math 모듈을 임포트하여 gcd 함수를 사용할 수 있도록 합니다.
  • lcm 함수를 정의합니다. 이 함수는 두 개의 정수를 매개변수로 받아서 최소 공배수를 계산하여 반환합니다.
  • 마지막으로 사용자 입력을 받아 함수를 테스트합니다.

테스트 케이스

이제 다양한 입력 값을 통해 함수가 제대로 작동하는지 확인해 보겠습니다.

# 테스트 케이스
print(lcm(4, 5))  # 출력: 20
print(lcm(12, 15))  # 출력: 60
print(lcm(7, 3))  # 출력: 21
print(lcm(100, 10))  # 출력: 100
print(lcm(27, 36))  # 출력: 108

복잡도 분석

이제 코드의 시간 복잡도와 공간 복잡도를 분석해 봅시다.

  • 시간 복잡도: GCD를 계산하는 데에 유클리드 알고리즘을 사용하면 O(log(min(A, B)))의 시간 복잡도를 가집니다. 따라서 LCM을 구하는 전체 복잡도는 O(log(min(A, B)))입니다.
  • 공간 복잡도: 상수 공간 O(1)입니다. 별도의 추가 메모리를 사용하지 않기 때문입니다.

마무리

이번 포스트에서는 두 개의 수를 이용해 최소 공배수를 구하는 알고리즘을 파이썬을 통해 구현해 보았습니다. 이번 문제는 공약수와 배수의 개념을 복습할 수 있는 좋은 기회가 되었을 것입니다. 코딩 테스트에서 자주 나타나는 유형이므로 충분히 연습하시기를 권장합니다.

다음 포스트에서는 더 다양한 문제를 가지고 찾아오겠습니다. 많은 관심 부탁드립니다!

참고 자료

파이썬 코딩테스트 강좌, 최대 공약수 구하기

안녕하세요! 오늘은 코딩테스트를 준비하는 여러분을 위해 “최대 공약수”를 구하는 알고리즘 문제를 다뤄보겠습니다. 최대 공약수를 적절하게 계산하는 것은 여러 문제에서 필수적으로 필요하며, 특히 수학적 사고와 알고리즘적 사고를 동시에 요구하는 문제입니다. 이번 시간에는 함수형 프로그래밍 기법을 사용할 것이며, Python 언어를 이용해 실습할 것입니다.

문제 설명

두 개의 정수 a, b가 주어질 때, 이 두 수의 최대 공약수를 구하는 프로그램을 작성해 주세요. 최대 공약수(Greatest Common Divisor, GCD)는 두 정수의 공통된 약수 중에서 가장 큰 수를 의미합니다.

입력

  • 첫 번째 줄에 두 개의 정수 a, b (1 ≤ a, b ≤ 109)가 주어진다.

출력

  • 정수 GCD(a, b)를 출력한다.

예제

다음은 몇 가지 예제입니다:

예제 1:
입력: 60 48
출력: 12

예제 2:
입력: 101 10
출력: 1

예제 3:
입력: 17 17
출력: 17

문제 해결 방법

최대 공약수를 구하는 방법 중 가장 유명한 방법은 유클리드 호제법입니다. 이 방법은 다음과 같은 원리에 기반합니다:

  • 두 수 ab의 최대 공약수는 bab로 나눈 나머지 r의 최대 공약수와 같다. 즉, GCD(a, b) = GCD(b, r)이다.
  • 이 과정을 반복하여 r가 0이 될 때까지 진행하면, 마지막에 남은 b가 최대 공약수이다.

유클리드 호제법 구현

이제 이제 유클리드 호제법을 파이썬 코드로 구현해보겠습니다. 아래 코드는 최대 공약수를 계산하는 함수를 정의한 예시입니다:

def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a

이 함수는 반복문을 사용하여 b가 0이 될 때까지 계속해서 두 수의 값을 교체하며 나머지를 계산합니다. 최종적으로 남는 a가 최대 공약수가 됩니다.

코드 실행 예

입력을 받아 실행하는 메인 코드를 작성하겠습니다:

if __name__ == "__main__":
    a, b = map(int, input("두 수를 입력하세요: ").split())
    result = gcd(a, b)
    print(f"최대 공약수: {result}")

결론

이번 글에서는 최대 공약수를 구하는 문제를 통해 유클리드 호제법의 원리를 배우고, 실제로 파이썬으로 구현해보았습니다. 이 문제는 다양한 응용이 가능하며, 다른 알고리즘 문제를 풀 때도 같은 원리를 적용할 수 있습니다. 알고리즘 문제를 해결하는 과정에서 수학과 프로그래밍의 조화를 경험해 보시길 바랍니다.

꼭 마무리로 말씀드리고 싶은 점은!

코딩테스트 준비의 기본은 다양한 문제를 많이 풀어보는 것입니다. 많은 문제들을 풀어보시고, 복습하시는 과정을 통해 코딩 실력을 한층 더 발전시킬 수 있을 것입니다. 감사합니다!