Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++로 균형 이진 탐색 트리(BST)에서 목표 합을 만족하는 쌍 찾기

문제 개요

균형 잡힌 이진 탐색 트리(BST)와 목표 합(target sum)이 주어졌을 때, 두 노드 값의 합이 목표 합과 일치하는 쌍(pair)이 존재하는지 확인하는 메서드를 구현해야 합니다. 이때 트리는 불변(immutable)이라는 점, 즉 트리의 구조나 값을 변경할 수 없다는 조건을 반드시 기억해야 합니다.

예를 들어 다음과 같은 트리가 입력으로 주어진다고 가정해 보겠습니다.

C++로 균형 이진 탐색 트리(BST)에서 목표 합을 만족하는 쌍 찾기

이 경우 출력 결과는 (9 + 26 = 35)가 됩니다.

해결 접근 방법

이 문제는 BST의 순회 특성을 활용하면 효율적으로 해결할 수 있습니다. 핵심 아이디어는 정렬된 배열의 양 끝에서 포인터를 안쪽으로 좁혀 가는 '투 포인터(two pointer)' 기법을 트리에 적용하는 것입니다.

구체적으로는 두 개의 독립적인 순회를 동시에 수행합니다.

  • 정방향 순회(중위 순회): 가장 작은 값부터 오름차순으로 노드를 하나씩 방문합니다.
  • 역방향 순회(역중위 순회): 가장 큰 값부터 내림차순으로 노드를 하나씩 방문합니다.

각 순회 상태는 스택을 사용하여 저장하며, 두 값의 합을 목표 합과 비교하면서 포인터를 이동시킵니다.

알고리즘 단계

  1. 두 개의 스택 s1, s2를 선언합니다.
  2. 플래그 변수 done1 := false, done2 := false로 초기화합니다.
  3. val1 := 0, val2 := 0으로 초기화합니다.
  4. curr1 := root, curr2 := root로 설정합니다.
  5. 무한 루프를 수행합니다.
    • 정방향 순회: done1이 false인 동안, curr1이 NULL이 아니면 curr1을 s1에 삽입하고 왼쪽 자식으로 이동합니다. curr1이 NULL이면 s1이 비어 있는 경우 done1을 true로 설정하고 종료하며, 그렇지 않으면 스택 최상단 노드를 꺼내 val1에 저장한 뒤 curr1을 오른쪽 자식으로 이동하고 done1을 true로 설정합니다.
    • 역방향 순회: done2가 false인 동안, 위 과정을 대칭으로 수행합니다. curr2가 NULL이 아니면 s2에 삽입하고 오른쪽 자식으로 이동하고, NULL이면 스택에서 노드를 꺼내 val2에 저장한 뒤 왼쪽 자식으로 이동합니다.
    • val1 ≠ val2이고 (val1 + val2) == target이면 해당 쌍을 출력하고 true를 반환합니다.
    • (val1 + val2) < target이면 더 큰 값이 필요하므로 done1 := false로 설정하여 정방향 순회를 진행합니다.
    • (val1 + val2) > target이면 더 작은 값이 필요하므로 done2 := false로 설정하여 역방향 순회를 진행합니다.
    • val1 ≥ val2이면 두 순회가 교차한 것이므로 더 이상 가능한 쌍이 없어 false를 반환합니다.

C++ 구현 예제

아래 구현 예제를 통해 더 잘 이해해 보겠습니다.

#include <bits/stdc++.h>
using namespace std;
#define MAX_SIZE 100
class TreeNode {
   public:
      int val;
      TreeNode *left, *right;
      TreeNode(int data) {
         val = data;
         left = NULL;
         right = NULL;
    }
};
bool isPairPresent(TreeNode* root, int target) {
   stack<TreeNode*> s1, s2;
   bool done1 = false, done2 = false;
   int val1 = 0, val2 = 0;
   TreeNode *curr1 = root, *curr2 = root;
   while (true) {
      while (done1 == false) {
         if (curr1 != NULL) {
            s1.push(curr1);
            curr1 = curr1->left;
         }
         else {
            if (s1.empty())
               done1 = 1;
            else {
               curr1 = s1.top();
               s1.pop();
               val1 = curr1->val;
               curr1 = curr1->right;
               done1 = 1;
            }
         }
      }
      while (done2 == false) {
         if (curr2 != NULL) {
            s2.push(curr2);
            curr2 = curr2->right;
         }
         else {
            if (s2.empty())
               done2 = 1;
            else {
               curr2 = s2.top();
               s2.pop();
               val2 = curr2->val;
               curr2 = curr2->left;
               done2 = 1;
            }
         }
      }
      if ((val1 != val2) && (val1 + val2) == target) {
         cout << "Pair Found: " << val1 << " + " << val2 << " = " << target << endl;
         return true;
      }
      else if ((val1 + val2) < target)
         done1 = false;
      else if ((val1 + val2) > target)
         done2 = false;
      if (val1 >= val2)
         return false;
   }
}
int main() {
   TreeNode* root = new TreeNode(16);
   root->left = new TreeNode(11);
   root->right = new TreeNode(21);
   root->left->left = new TreeNode(9);
   root->left->right = new TreeNode(13);
   root->right->left = new TreeNode(17);
   root->right->right = new TreeNode(26);
   int target = 35;
   cout << (isPairPresent(root, target));
}

입력

TreeNode* root = new TreeNode(16);
root->left = new TreeNode(11);
root->right = new TreeNode(21);
root->left->left = new TreeNode(9);
root->left->right = new TreeNode(13);
root->right->left = new TreeNode(17);
root->right->right = new TreeNode(26);

출력

Pair Found: 9 + 26 = 35
1

복잡도 분석

  • 시간 복잡도: O(n) — 각 순회는 트리의 모든 노드를 최대 한 번씩만 방문합니다.
  • 공간 복잡도: O(h) — h는 트리의 높이이며, 두 개의 스택에는 각 순회 경로의 노드만 저장됩니다.

트리를 배열로 변환하지 않고도 O(h)의 추가 메모리만으로 문제를 해결할 수 있다는 점에서, 이 방법은 불변 BST 환경에서 매우 효율적인 접근 방식입니다.