파이썬 코딩테스트 강좌, 트리의 부모 찾기

안녕하세요! 이번 포스트에서는 트리 구조의 데이터를 다루는 알고리즘 문제를 풀어보겠습니다. 우리는 “트리의 부모 찾기”라는 문제를 통해 트리를 이해하고, 이를 통해 파이썬 프로그래밍 능력을 향상시킬 수 있을 것입니다. 트리 구조는 컴퓨터 과학에서 아주 중요한 개념 중 하나이며, 이는 데이터베이스, 파일 시스템, 웹사이트의 구조 등 다양한 곳에 응용되고 있습니다.

문제 설명

문제: 주어진 정점의 부모 노드를 찾는 함수를 작성하세요. 트리는 노드와 엣지로 구성된 비선형 자료구조로, 각 노드는 0개 이상의 자식 노드를 가질 수 있습니다. 입력으로는 노드의 수와 각 노드의 부모 노드 정보가 주어집니다. 우리는 특정 노드의 부모 노드를 반환하는 함수를 작성해야 합니다.

입력 형식:
– 첫 번째 줄에 노드의 수 N (1 ≤ N ≤ 100,000)이 주어집니다.
– 그 다음 N-1 줄에 각 줄마다 두 개의 정수 A, B가 주어지며, 이는 A가 B의 부모임을 의미합니다.

출력 형식: 특정 노드의 부모 노드 번호를 출력합니다.

문제 해결 방안

이 문제를 해결하기 위해서는 먼저 주어진 정보를 바탕으로 트리 구조를 형성할 수 있어야 합니다. 다음 과정을 따라 문제를 해결하겠습니다.

1단계: 데이터 구조 선택

트리를 구현하기 위해 딕셔너리를 사용합니다. 각 노드의 번호를 키로 하고, 해당 노드의 부모 노드를 값으로 가지는 구조를 선택하겠습니다. 그러면 주어진 관계를 효율적으로 저장하고 빠르게 부모 노드를 찾을 수 있습니다.

2단계: 입력 데이터 처리

입력 데이터를 읽어서 트리 구조를 생성합니다. 노드의 수를 입력받고, N-1 줄에 걸쳐 부모-자식 관계를 추가합니다. 이를 통해 전체 트리를 구성할 수 있습니다.

3단계: 부모 노드 찾기

특정 노드의 부모 노드를 찾기 위해, 앞서 만든 딕셔너리에서 해당 노드의 부모를 바로 조회하면 됩니다. 이는 상수 시간 (`O(1)`)에 가능합니다.

4단계: 함수 작성

위에서 논의한 내용을 바탕으로 파이썬 함수를 작성해 보겠습니다. 다음은 문제 해결을 위한 코드입니다:


def find_parent(n, edges, query_node):
    # 부모 노드 정보를 담는 딕셔너리
    parent = {}
    
    # 입력으로 주어진 관계를 기반으로 딕셔너리 생성
    for a, b in edges:
        parent[b] = a
        
    # 특정 노드의 부모 노드를 요청받았을 때 반환
    return parent.get(query_node, None)

# 예시 입력
n = 7  # 노드 수
edges = [(1, 2), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)]  # 부모-자식 관계
query_node = 4  # 부모를 찾고자 하는 노드

# 부모 노드 찾기
print(find_parent(n, edges, query_node))

전체 코드


def find_parent(n, edges, query_node):
    parent = {}
    
    # 관계를 딕셔너리로 저장
    for a, b in edges:
        parent[b] = a
    
    return parent.get(query_node, None)

if __name__ == "__main__":
    n = int(input("노드 수를 입력하세요: "))
    edges = []
    for _ in range(n - 1):
        a, b = map(int, input("부모와 자식 관계를 입력하세요 (예: 'A B'): ").split())
        edges.append((a, b))
    
    query_node = int(input("부모를 찾고자 하는 노드 번호를 입력하세요: "))
    result = find_parent(n, edges, query_node)
    
    if result is not None:
        print(f"노드 {query_node}의 부모 노드는 {result}입니다.")
    else:
        print(f"노드 {query_node}의 부모 노드를 찾을 수 없습니다.")

문제 해결 과정 분석

위 코드는 다음과 같은 구조로 문제를 해결합니다:

  • 딕셔너리를 사용하여 부모 노드 정보를 효율적으로 저장합니다.
  • 주어진 관계를 통해 트리 구조를 형성합니다.
  • 특정 노드에 대해 부모 노드를 빠르게 조회할 수 있습니다.

복잡도 분석

– 시간 복잡도: `O(N)` \- 노드 수(N)에 비례하여 부모 노드 관계를 저장합니다.
– 공간 복잡도: `O(N)` \- 노드 정보를 저장하기 위해 딕셔너리를 사용합니다.

결론

이번 포스트에서는 “트리의 부모 찾기”라는 문제를 통해 트리 구조와 관련된 기본적인 알고리즘을 배웠습니다. 트리는 효율적인 데이터 탐색 및 데이터 저장 방식으로 요즘 데이터 과학과 소프트웨어 개발에서 많이 사용되는 자료형입니다. 이 문제를 통해 트리를 이해하는 데 훌륭한 기초가 되었으리라 생각합니다. 다음 포스트에서는 더 복잡한 트리 문제를 다루어 보겠습니다. 감사합니다!

파이썬 코딩테스트 강좌, 트리 알아보기

이 글에서는 트리 데이터 구조의 개념과 해당 구조를 기반으로 한 알고리즘 문제를 다루고, 이를 해결하는 과정을 상세히 설명하겠습니다.

트리란?

트리는 비선형 데이터 구조의 일종으로, 계층적인 관계를 표현하는 데 사용됩니다. 트리는 다음과 같은 특징을 가집니다:

  • 트리는 노드의 집합으로 구성됩니다.
  • 하나의 루트 노드가 있으며, 나머지 노드는 이 루트 노드를 기준으로 하위 트리를 형성합니다.
  • 각 노드는 0개 이상의 자식 노드를 가질 수 있습니다.
  • 트리는 사이클이 없습니다.

어떤 종류의 트리들이 있을까요?

트리는 그 구조와 규칙에 따라 여러 종류로 나눌 수 있습니다. 다음은 몇 가지 주요 트리의 종류입니다:

  • 이진 트리: 각 노드가 최대 2개의 자식 노드를 가지는 트리입니다.
  • 완전 이진 트리: 모든 레벨이 최대 노드 수를 가진 트리입니다.
  • 균형 이진 트리: 높이 차이가 최소화된 이진 트리입니다.
  • 이진 탐색 트리(BST): 왼쪽 서브트리의 모든 값은 부모보다 작고, 오른쪽 서브트리의 모든 값은 부모보다 큰 트리입니다.
  • AVL 트리: 균형 잡힌 이진 탐색 트리의 일종입니다.

알고리즘 문제: 이진 트리의 최대 깊이

다음 문제를 해결해보겠습니다.

문제: 이진 트리가 주어질 때, 이 트리의 최대 깊이를 구하는 함수를 작성하시오. 트리의 깊이는 루트 노드에서 가장 깊은 리프 노드까지의 경로에 있는 노드의 수입니다.

예를 들어, 주어진 트리가 다음과 같을 때:

            3
           / \
          9  20
            /  \
           15   7
        

이 트리의 최대 깊이는 3입니다.

문제 해결 접근법

이 문제를 해결하기 위해, 우리는 트리의 노드를 탐색하고 각 노드의 깊이를 계산할 수 있습니다. 깊이를 계산하는 방법으로는 여러 가지가 있지만, 깊이 우선 탐색(DFS)을 사용하면 간단하게 해결할 수 있습니다. 재귀적 접근법을 이용하면 코드가 간결해집니다.

파이썬 코드 구현

아래는 주어진 문제를 해결하기 위한 파이썬 코드입니다:

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

def maxDepth(root):
    if not root:
        return 0
    else:
        left_depth = maxDepth(root.left)
        right_depth = maxDepth(root.right)
        return max(left_depth, right_depth) + 1

        

위의 코드는 이진 트리의 루트 노드를 인자로 받아서 최대 깊이를 반환합니다. 재귀 함수를 사용하여 현재 노드가 비어있지 않은 경우, 왼쪽과 오른쪽 서브트리의 최대 깊이를 계산하고, 더 큰 깊이에 1을 더하여 반환합니다.

코드 실행 예시

아래는 트리를 생성하고, 최대 깊이를 계산하는 예시입니다:

root = TreeNode(3)
root.left = TreeNode(9)
root.right = TreeNode(20)
root.right.left = TreeNode(15)
root.right.right = TreeNode(7)

print(maxDepth(root))  # 출력 결과: 3

        

위 코드를 실행하면, 주어진 이진 트리의 최대 깊이는 3이라는 결과를 얻습니다.

심화 학습: 트리의 다른 탐색 방법

트리 탐색에는 DFS 외에도 너비 우선 탐색(BFS) 방법이 있습니다. BFS는 큐를 사용하여 레벨 순서대로 노드를 탐색하는 방식입니다. 아래는 BFS를 사용하여 최대 깊이를 계산하는 방법입니다.

from collections import deque

def maxDepthBFS(root):
    if not root:
        return 0
    queue = deque([root])
    depth = 0
    while queue:
        depth += 1
        level_size = len(queue)
        for _ in range(level_size):
            node = queue.popleft()
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
    return depth

        

BFS 접근법을 사용하게 되면, 각 레벨을 반복하면서 노드를 탐색하므로 전체 깊이를 효율적으로 계산할 수 있습니다.

트리 문제 풀이의 중요성

코딩 테스트에서 트리 문제는 자주 출제됩니다. 트리는 복잡한 데이터 구조이므로, 이에 대한 이해가 없으면 어려운 문제를 해결하기 어렵습니다. 트리 문제를 통해 재귀, BFS, DFS, 백트래킹 등 다양한 알고리즘 문제 해결 전략을 익힐 수 있습니다. 따라서, 기업의 코딩 테스트에 대비하기 위해 트리 문제를 충분히 연습하는 것이 중요합니다.

결론

이번 글에서는 이진 트리의 개념과 최대 깊이를 구하는 알고리즘 문제를 통해 깊이 우선 탐색 및 너비 우선 탐색의 두 가지 접근 방식을 살펴보았습니다. 트리 문제는 알고리즘 문제의 기본이며, 실제 코딩 테스트에서도 자주 출제되는 중요한 주제입니다. 따라서, 이와 관련된 다양한 문제를 풀어보며 경험을 쌓는 것이 중요합니다.

트리와 관련된 문제를 풀어보면서 다양한 경험을 쌓고, 알고리즘 이해도를 높이는 계기가 되길 바랍니다. 앞으로도 다양한 자료 구조와 알고리즘에 대해 깊이 있게 공부해보세요.

파이썬 코딩테스트 강좌, 트라이

1. 서론

안녕하세요! 오늘은 취업 준비생들에게 매우 유용한 트라이(Trie) 자료구조에 대해 알아보겠습니다. 여러 알고리즘 문제를 해결하기 위해서는 다양한 자료구조를 이해하고 활용할 수 있어야 하는데, 그 중 트라이 자료구조는 문자열 관련 문제를 효과적으로 해결하는 데 매우 강력합니다. 이번 포스트에서는 트라이의 개념, 특성, 활용 예시, 그리고 이를 통해 해결할 수 있는 문제를 다뤄보겠습니다.

2. 트라이(Trie) 자료구조란?

트라이는 다수의 문자열을 저장하는 데 최적화된 트리 구조입니다. 주로 문자열의 접두사(Prefix) 검색에 사용됩니다. 각 노드는 일반적으로 문자열의 문자 하나를 나타내며, 루트 노드에서 시작하여 자식 노드로 진행함으로써 문자열의 각 문자를 단계적으로 해석할 수 있습니다. 트라이는 다음과 같은 특징을 가지고 있습니다.

  • 트라이의 각 노드는 문자와 해당 문자의 자식 노드를 가집니다.
  • 속한 노드의 경로를 통해 문자열을 구성합니다.
  • 중복된 문자열은 하나의 경로로 통합됩니다.
  • 각 노드에서 종료 노드를 표시하여 문자열의 끝을 나타낼 수 있습니다.

3. 트라이 구조

트라이 구조는 다음과 같이 설계될 수 있습니다. 각 노드는 한 글자에 해당하며, 루트 노드부터 아래로 내려가며 문자열을 형성합니다. 예를 들어, 문자열 ‘cat’, ‘car’를 추가하면 다음과 같은 구조가 형성됩니다:

        Root
        └── c
            ├── a
            │   ├── t (종료 노드)
            │   └── r (종료 노드)
        

위의 구조를 통해 ‘ca’ 접두사를 가지는 문자열을 쉽게 탐색할 수 있습니다.

4. 트라이 구현하기

트라이를 구현하기 위해 먼저 클래스와 메소드를 정의해 보겠습니다. 기본적인 구현은 노드를 정의하고, 삽입, 검색, 삭제의 기능을 갖춘 트라이 클래스를 만드는 것입니다.

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end_of_word = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end_of_word = True

    def search(self, word: str) -> bool:
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]
        return node.is_end_of_word

    def starts_with(self, prefix: str) -> bool:
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True
        

위의 코드는 트라이 노드를 나타내는 TrieNode 클래스와, 기본적인 트라이 기능을 포함한 Trie 클래스를 정의합니다.

5. 문제: 문자열 목록에서 접두사를 찾기

이번에는 트라이를 활용하여 ‘접두사 검색’ 문제를 해결해 보겠습니다. 문제는 다음과 같습니다.

문제 설명

주어진 문자열의 목록이 있을 때, 특정 문자열이 이 목록의 어떤 문자열의 접두사인지 확인하는 프로그램을 작성하세요.

입력

- 문자열 목록: ["apple", "banana", "app", "apricot"]
- 접두사: "app" 

출력

'app'는 목록의 'apple'과 'app'의 접두사입니다.

이제 이 문제를 해결하기 위해 트라이를 활용해 보겠습니다.

6. 문제 해결 과정

트라이를 사용하여 이 문제를 해결해 보겠습니다. 먼저 문자열 목록을 트라이에 삽입한 후, 주어진 접두사가 트라이에 포함되어 있는지 확인합니다.

def find_prefix_words(words: List[str], prefix: str) -> List[str]:
    trie = Trie()
    for word in words:
        trie.insert(word)

    result = []
    for word in words:
        if word.startswith(prefix):
            result.append(word)
    return result

# 사용 예제
words = ["apple", "banana", "app", "apricot"]
prefix = "app"
print(find_prefix_words(words, prefix))
        

위의 코드는 문자열 목록을 입력받아, 해당 목록의 접두사를 찾는 기능을 구현한 것입니다. 사용자가 입력한 접두사로 시작하는 모든 문자열을 반환하도록 하였습니다.

7. 결론

트라이는 문자열 데이터 처리와 검색에서 큰 성능 향상을 가져야 합니다. 접두사 검색과 같은 문제를 해결하는 데 매우 강력한 도구입니다. 본 포스트에서 살펴본 것처럼, 트라이를 사용하면 문자 기반의 데이터를 효과적으로 다룰 수 있습니다. 오늘 포스트가 트라이 자료구조를 이해하는 데 도움이 되었길 바랍니다.

앞으로 더 많은 알고리즘 문제를 다루는 포스트를 기대해주세요!

파이썬 코딩테스트 강좌, 트리 순회하기

소개

알고리즘 문제 해결은 소프트웨어 개발자에게 매우 중요한 과정입니다. 특히, 트리는 많은 문제의 핵심 구조로 사용되며,
트리를 순회하는 방법을 이해하는 것은 이 구조를 효과적으로 활용하는 데 필수적입니다.
이 글에서는 트리의 기본 개념과 파이썬을 이용한 다양한 순회 방법에 대해 설명하고, 실제 알고리즘 문제를 해결해보는
과정을 통해 이론을 실습으로 연결해보겠습니다.

트리의 기초

트리는 노드와 노드 간의 연결로 이루어진 비선형 자료구조입니다.
각각의 노드는 자식 노드를 가질 수 있으며, 이 구조는 계층성을 표현하기에 적합합니다.
가장 위에 있는 노드를 루트 노드라고 하며, 노드 간의 관계를 유도하는 다양한 탐색 방법이 존재합니다.

트리의 종류

– 이진 트리: 각 노드가 최대 두 개의 자식 노드를 가지는 트리
– 이진 탐색 트리: 정렬된 이진 트리로, 왼쪽 자식은 부모 노드보다 작고, 오른쪽 자식은 크다
– AVL 트리: 균형 이진 탐색 트리로, 높이 차이가 1 이하로 유지된다

트리 순회 방식

트리 순회는 노드를 방문하는 순서를 정의합니다. 주요 순회 방식은 다음과 같습니다.

1. 전위 순회 (Pre-Order Traversal)

전위 순회는 노드 자체를 먼저 방문한 후, 왼쪽 자식 노드를 재귀적으로 방문하고, 이후 오른쪽 자식 노드를 방문하는 방법입니다.
즉, 방문 순서는 다음과 같습니다: 노드 → 왼쪽 → 오른쪽

2. 중위 순회 (In-Order Traversal)

중위 순회는 먼저 왼쪽 자식 노드를 방문한 후, 노드를 방문하고 마지막으로 오른쪽 자식 노드를 방문하는 방식입니다.
순서는 다음과 같습니다: 왼쪽 → 노드 → 오른쪽

3. 후위 순회 (Post-Order Traversal)

후위 순회는 왼쪽 자식 노드를 방문하고 오른쪽 자식 노드를 방문한 후, 마지막으로 노드를 방문하는 방법입니다.
순서는 다음과 같습니다: 왼쪽 → 오른쪽 → 노드

문제: 이진 트리 순회 결과값 출력하기

다음은 이진 트리의 노드를 입력받아 전위, 중위 및 후위 순회의 결과를 출력하는 문제입니다.
입력으로는 각 노드의 값을 담고 있는 리스트가 주어질 때, 트리의 순회 결과를 리턴해야 합니다.

문제 설명

주어진 리스트를 기반으로 이진 트리를 구성한 후, 각 순회 방법을 이용하여 결과를 담은 리스트를 반환합니다.
가령, 리스트가 [1, 2, 3, 4, 5]라면, 다음과 같은 트리를 구성할 수 있습니다:

                 1
                / \
               2   3
              / \
             4   5
            

입력 / 출력 형식

  • 입력: 각 노드를 담고 있는 리스트가 주어짐.
  • 출력: 전위, 중위 및 후위 순회 결과 리스트

문제 해결 과정

1단계: 트리 구조 정의하기

트리를 구성하기 위해 먼저 노드를 나타내는 클래스를 정의하겠습니다.

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

2단계: 트리 구축하기

주어진 리스트를 기반으로 트리를 구축하는 함수를 작성합니다.
이 예제에서는 간단히 리스트의 첫 번째 값을 루트 노드로 설정하고 나머지를 왼쪽 자식과 오른쪽 자식으로 배치합니다.

def build_tree(values):
    if not values:
        return None
    root = TreeNode(values[0])
    for value in values[1:]:
        insert_node(root, value)
    return root

def insert_node(root, value):
    if value < root.value:
        if root.left is None:
            root.left = TreeNode(value)
        else:
            insert_node(root.left, value)
    else:
        if root.right is None:
            root.right = TreeNode(value)
        else:
            insert_node(root.right, value)
            

3단계: 순회 함수 작성하기

각각의 순회 방식을 구현합니다. 재귀적으로 트리를 탐색하며 노드를 리스트에 저장합니다.

def preorder_traversal(root):
    result = []
    if root:
        result.append(root.value)
        result.extend(preorder_traversal(root.left))
        result.extend(preorder_traversal(root.right))
    return result

def inorder_traversal(root):
    result = []
    if root:
        result.extend(inorder_traversal(root.left))
        result.append(root.value)
        result.extend(inorder_traversal(root.right))
    return result

def postorder_traversal(root):
    result = []
    if root:
        result.extend(postorder_traversal(root.left))
        result.extend(postorder_traversal(root.right))
        result.append(root.value)
    return result
            

4단계: 통합하여 호출하기

최종적으로 위의 모든 함수를 통합하여 문제를 해결합니다.

def traverse_tree(values):
    root = build_tree(values)
    return {
        'preorder': preorder_traversal(root),
        'inorder': inorder_traversal(root),
        'postorder': postorder_traversal(root),
    }

# 예제 사용
input_values = [1, 2, 3, 4, 5]
result = traverse_tree(input_values)
print(result)  # 결과 출력
            

최종 결과 및 정리

위의 과정을 통해 우리는 입력받은 이진 트리 리스트를 순회하여 전위, 중위, 후위 순회의 결과를 도출할 수 있습니다.
이 문제를 통해 트리 순회의 개념과 이를 효율적으로 구현하는 방법에 대해 배웠습니다.
추가적으로 다양한 트리 구조와 순회 방식에 대해서도 경험하고 실습해보기를 권장합니다.

파이썬 코딩테스트 강좌, 투 포인터

안녕하세요, 여러분! 오늘은 “투 포인터” 알고리즘에 대해 자세히 알아보고, 이 알고리즘을 활용한 문제를 풀어보는 시간을 가지겠습니다. 투 포인터 기법은 배열이나 리스트와 같이 정해진 크기를 가진 데이터 구조에서 문제를 해결할 때 매우 유용한 방법입니다. 각각의 포인터는 데이터 구조의 위치를 식별하는 용도로 사용되며, 이를 통해 시간 복잡도를 줄일 수 있습니다.

1. 투 포인터 알고리즘이란?

투 포인터 알고리즘은 주어진 배열에서 두 지점을 동시에 사용할 때 효율적으로 문제를 해결하기 위한 대표적인 기법입니다. 주로 정렬된 배열에서 부분합, 최대/최소값 문제를 해결하거나, 특정 조건을 만족하는 데이터 쌍을 찾을 때 유용하게 사용됩니다.

투 포인터의 장점은 반복문 하나로 배열을 한 번만 순회하면서 필요한 결과를 얻을 수 있다는 점입니다. 이를 통하여 O(n)의 시간 복잡도로 문제를 해결할 수 있습니다.

2. 문제 설명

이번에 풀어볼 문제는 “정수 배열에서 이상적인 쌍 찾기”입니다. 주어진 정수 배열 arr와 정수 K가 있을 때, 느낌표(!) 없이 K의 합이 되는 두 수를 찾아라. 이 때의 두 수의 인덱스를 반환하는 함수를 작성하세요. 만약 such a pair가 없다면, -1을 반환합니다.

문제 예시

  • 입력: arr = [10, 15, 3, 7], K = 17
  • 출력: (0, 2) // 인덱스는 0 기준
  • 입력: arr = [10, 15, 3, 7], K = 20
  • 출력: -1

3. 접근 방법

이 문제를 해결하는 데 있어 수학적 접근을 사용할 수 있습니다. 두 수의 합이 K가 되어야 하므로, 첫 번째 포인터는 배열의 시작을 가리키고, 두 번째 포인터는 배열의 끝을 가리킵니다. 그리고 두 포인터가 가리키는 값의 합을 계산한 후, 이 합이 K와 비교됩니다.

1. 먼저 첫 번째 포인터를 배열의 가장 왼쪽, 두 번째 포인터를 배열의 가장 오른쪽에 위치시킵니다.

2. 두 포인터가 서로 교차할 때까지 반복합니다:

  • 합계가 K와 같으면 두 포인터의 인덱스를 반환합니다.
  • 합계가 K보다 작으면, 첫 번째 포인터를 오른쪽으로 이동하여 합계를 증가시킵니다.
  • 합계가 K보다 크면, 두 번째 포인터를 왼쪽으로 이동하여 합계를 감소시킵니다.

3. 만약 두 포인터가 교차했지만 쌍을 찾지 못했다면, -1을 반환합니다.

4. 코드 구현

  
def two_sum(arr, K):
    left = 0
    right = len(arr) - 1
    
    while left < right:
        current_sum = arr[left] + arr[right]
        
        if current_sum == K:
            return (left, right)  # 쌍을 찾음
        elif current_sum < K:
            left += 1  # 더 큰 합을 위해 왼쪽 포인터 이동
        else:
            right -= 1  # 더 작은 합을 위해 오른쪽 포인터 이동

    return -1  # 쌍을 찾지 못함
  
  

5. 코드 설명

위의 코드는 투자녀의 두 포인터 기법을 사용하여 두 수의 합으로 K가 되는 인덱스 쌍을 찾는 함수입니다. leftright 두 변수를 사용하여 포인터를 배열의 양 끝에서부터 시작합니다.

먼저 while 루프에서 두 포인터가 방향성으로 개선되고 있는지 확인합니다. 현재의 두 값의 합을 계산한 후, 이 값이 K와 같다면 해당 인덱스를 반환합니다. 그렇지 않다면 합의 크기에 따라 포인터를 조정하고 반복합니다.

6. 시간 복잡도 분석

이 알고리즘의 시간복잡도는 O(n)입니다. 두 포인터가 배열의 양 끝에서 시작하므로, 배열을 전부 순회할 필요가 없이 조건을 만족하기 위해 단 한번의 반복으로 끝나게 됩니다. 공간복잡도는 O(1)로 상수 공간만 사용하는 매우 효율적인 방법입니다.

7. 추가 예제

테스트 케이스를 통해 코드의 올바름을 검증할 수 있습니다.

  
print(two_sum([10, 15, 3, 7], 17))  # 출력: (0, 2)
print(two_sum([10, 15, 3, 7], 20))  # 출력: -1
print(two_sum([1, 2, 3, 4, 6], 10))  # 출력: (3, 4)
  
  

8. 마무리

오늘은 투 포인터 기법을 사용하여 문제를 해결해보았습니다. 파이썬 알고리즘 문제를 풀 때, 이러한 기법을 활용하면 시간을 절약하고 효율적으로 문제를 해결할 수 있습니다. 다양한 문제에서 연습하여 더 많은 노하우를 쌓아보세요. 다음 시간에는 다이나믹 프로그래밍에 대해 다뤄보겠습니다. 오늘도 수고하셨습니다!