코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기

2026.09.10·수정 2026.09.13·약 13분·작성: 해비·블로그 소개

[창작 문제] 기준 이상 등록번호 안내

등록번호를 순서 없이 추가하면서 방문자가 제시한 기준 이상인 가장 작은 번호를 안내합니다. 중복 등록은 한 번으로 취급하며 없는 답은 null입니다.

일곱 노드로 이루어진 균형 이진 트리 개념 표지
주제를 표현한 개념 표지입니다. 정확한 동작과 값의 변화는 아래 코드와 표에서 확인하세요.

이 문제는 BlogFlow 자료구조 학습용으로 독립 창작했습니다. 외부 문제의 지문이나 테스트를 옮기지 않았습니다.

문제

전시 안내 데스크는 등록된 작품 번호를 관리합니다. add 작업은 번호를 등록하고 next 작업은 기준 이상인 등록번호 중 가장 작은 번호를 묻습니다. 기준과 같은 번호가 있으면 그 번호가 답입니다. 어떤 등록번호도 기준 이상이 아니면 null을 반환하세요. 등록 취소 작업은 없습니다.

관련 구현 설명: JavaScript AVL 트리: 높이 불변식과 LL·RR·LR·RL 삽입 회전

입력·출력과 제약

JSON 객체 {ops}. 작업은 ["add",key] 또는 ["next",threshold]입니다.

next 작업의 답을 순서대로 담은 JSON 배열입니다. 각 답은 정수 또는 null입니다.

작업 수 0~100,000. 번호와 기준은 정수이고 절댓값 1,000,000,000 이하입니다. 같은 번호를 여러 번 등록할 수 있지만 집합 원소 수는 한 번만 증가합니다.

입력은 위 계약을 만족한다고 가정합니다. 온라인 채점기는 제공하지 않으며 로컬 Node.js에서 JSON 입력으로 실행합니다.

예시

예시 1 입력:

{"ops":[["next",15],["add",30],["add",10],["add",20],["add",20],["next",15],["next",30],["next",31]]}

예시 1 출력:

[null,20,30,null]

예시 2 입력:

{"ops":[]}

예시 2 출력:

[]

힌트

현재 노드가 기준 이상이면 후보로 기록하고 왼쪽에서 더 작은 답을 찾으세요. 현재 노드가 기준 미만이면 왼쪽은 볼 필요가 없습니다.

정답과 해설

정답 코드와 해설 펼치기

Node.js 24 CommonJS용 전체 풀이입니다. avl-problem.cjs로 저장하세요. 클래스 선언, solve 함수, 표준 입력 처리가 모두 들어 있어 가이드 파일을 별도로 가져오지 않습니다. 예시 입력을 input.json에 저장하고 터미널에서 실행합니다.

Windows PowerShell:
Get-Content -Raw -Encoding UTF8 .input.json | node .avl-problem.cjs

macOS / Linux:
node avl-problem.cjs < input.json
class AVLSet {
  constructor() {
    this.root = null;
    this.size = 0;
  }
  check(key) {
    if (!Number.isSafeInteger(key)) throw new TypeError('키는 안전한 정수여야 합니다.');
  }
  height(node) { return node?.height ?? 0; }
  update(node) {
    node.height = 1 + Math.max(this.height(node.left), this.height(node.right));
  }
  rotateRight(y) {
    const x = y.left, middle = x.right;
    x.right = y;
    y.left = middle;
    this.update(y);
    this.update(x);
    return x;
  }
  rotateLeft(x) {
    const y = x.right, middle = y.left;
    y.left = x;
    x.right = middle;
    this.update(x);
    this.update(y);
    return y;
  }
  add(key) {
    this.check(key);
    const before = this.size;
    this.root = this.insert(this.root, key);
    return this.size !== before;
  }
  insert(node, key) {
    if (!node) {
      this.size++;
      return { key, left: null, right: null, height: 1 };
    }
    if (key < node.key) node.left = this.insert(node.left, key);
    else if (key > node.key) node.right = this.insert(node.right, key);
    else return node;
    this.update(node);
    const balance = this.height(node.left) - this.height(node.right);
    if (balance > 1) {
      if (key > node.left.key) node.left = this.rotateLeft(node.left);
      return this.rotateRight(node);
    }
    if (balance < -1) {
      if (key < node.right.key) node.right = this.rotateRight(node.right);
      return this.rotateLeft(node);
    }
    return node;
  }
  has(key) {
    this.check(key);
    let node = this.root;
    while (node) {
      if (key === node.key) return true;
      node = key < node.key ? node.left : node.right;
    }
    return false;
  }
  ceiling(key) {
    this.check(key);
    let node = this.root, candidate = null;
    while (node) {
      if (node.key >= key) {
        candidate = node.key;
        node = node.left;
      } else node = node.right;
    }
    return candidate;
  }
  toArray() {
    const result = [];
    const visit = (node) => {
      if (!node) return;
      visit(node.left);
      result.push(node.key);
      visit(node.right);
    };
    visit(this.root);
    return result;
  }
}

function solve({ ops }) {
  const tree = new AVLSet(), out = [];
  for (const [type, key] of ops) {
    if (type === 'add') tree.add(key);
    else if (type === 'next') out.push(tree.ceiling(key));
    else throw new Error('알 수 없는 작업입니다.');
  }
  return out;
}

module.exports = { solve };

if (require.main === module) {
  const input = JSON.parse(require('node:fs').readFileSync(0, 'utf8'));
  console.log(JSON.stringify(solve(input)));
}

아직 번호가 없을 때 next(15)는 null입니다. 30,10,20 등록은 LR 회전을 만들고 최종 루트가 20이 됩니다. 20을 다시 넣어도 집합은 {10,20,30}입니다. 기준 15의 최소 후보는 20, 기준 30의 후보는 자기 자신 30, 기준 31에는 후보가 없습니다. solve는 등록 시 add, 질의 시 ceiling만 호출하므로 전체 정렬을 반복하지 않습니다.

모든 노드에서 왼쪽 키는 node.key보다 작고 오른쪽 키는 큽니다. 중복은 새 노드로 만들지 않으므로 엄격한 부등호를 쓸 수 있습니다. 동시에 node.height는 1+max(왼쪽 높이,오른쪽 높이)이고, 균형 인수 balance=왼쪽 높이−오른쪽 높이는 −1,0,1이어야 합니다.

삽입은 먼저 일반 BST와 같은 한 경로를 내려갑니다. 재귀가 돌아올 때 그 경로의 높이를 다시 계산하고 균형을 고칩니다. 회전은 중위 순서를 바꾸지 않고 부모·자식 관계만 재배치합니다. 오른쪽 회전에서 x.right였던 middle은 y.left로 옮겨야 모든 키가 x< middle <y 순서를 보존합니다.

q개 작업, 최대로 저장한 서로 다른 키 수 u에 대해 최악 O(q log(u+1)) 시간입니다. 구조 O(u), 삽입 재귀 O(log(u+1)), 반환 결과 O(q) 공간이며 삭제 비용은 포함하지 않습니다.

표본과 경계를 직접 확인하려면 다음 코드를 avl-check.cjs로 저장하고 같은 폴더에서 node avl-check.cjs를 실행하세요. assert가 맞으면 출력 없이 종료하고, 다르면 예외와 함께 실패합니다.

const assert = require('node:assert/strict');
const { solve } = require('./avl-problem.cjs');

assert.deepEqual(
  solve({
  "ops": [
    [
      "next",
      15
    ],
    [
      "add",
      30
    ],
    [
      "add",
      10
    ],
    [
      "add",
      20
    ],
    [
      "add",
      20
    ],
    [
      "next",
      15
    ],
    [
      "next",
      30
    ],
    [
      "next",
      31
    ]
  ]
}),
  [null,20,30,null],
);
assert.deepEqual(
  solve({
  "ops": []
}),
  [],
);

연결 학습과 공식 자료

JavaScript AVL 트리: 높이 불변식과 LL·RR·LR·RL 삽입 회전에서 연산별 이유와 전체 복잡도를 이어서 확인할 수 있습니다. 다른 형식의 공식 연습은 LeetCode 1382 – Balance a Binary Search Tree를 참고하세요. 외부 문제의 정답이나 지문은 이 페이지에 복제하지 않았습니다.

공식 자료 확인일: 2026년 9월 10일. 구현은 이 글의 JavaScript 계약에 맞춰 독립적으로 작성했습니다. 다른 언어 라이브러리의 API와 숫자 범위가 그대로 적용되는 것은 아닙니다.

풀이 전에 확인할 순서

  1. 입력값과 출력값을 한 문장으로 다시 적습니다.
  2. 반복할 대상과 비교·저장할 값을 정합니다.
  3. 필요한 자료구조와 시간복잡도를 예상합니다.
  4. 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기

정답 코드를 바로 따라 쓰기보다, 본문에서 값이 갱신되는 조건과 반복 범위를 먼저 찾으세요. 반복 한 번마다 반드시 유지되어야 하는 값이 무엇인지 적으면 풀이의 중심 변수를 고르기 쉽습니다.

테스트 확인

  • 가능한 가장 작은 입력
  • 같은 값이나 문자가 반복되는 입력
  • 정답이 처음 또는 마지막 위치에서 결정되는 입력
  • 입력 제한에 가까운 경우의 실행 시간

확인 결과: 본문의 예제뿐 아니라 위 경계 사례에서도 예상값과 실제 출력이 같아야 풀이가 완료됩니다.

이 글이 도움이 되었나요?

조회 중

코딩테스트 JavaScript 학습 순서

필수 49개 · 전체 49개

읽음 기록 관리

전체 과정 목차 (49개)
  1. 필수 길잡이 · 코딩테스트 JS 자료구조 로드맵: 배열, 해시, 스택, 투 포인터 순서
  2. 필수 학습 · 세 수 중 최솟값 JavaScript 조건문 풀이 정리
  3. 필수 학습 · 삼각형 판별하기 JavaScript 풀이
  4. 필수 학습 · 연필 개수 JavaScript 풀이
  5. 필수 학습 · 1부터 N까지 합 출력하기 JavaScript 풀이
  6. 필수 학습 · 최솟값 구하기 JavaScript 풀이|배열 순회와 비교 갱신 원리
  7. 필수 학습 · 홀수 JavaScript 풀이: 조건 판별과 결과 처리 정리
  8. 필수 학습 · 10부제 JavaScript 풀이: 끝자리 비교로 위반 차량 수 세기
  9. 필수 학습 · A를 #으로 JavaScript 풀이: 문자열 순회와 치환
  10. 필수 학습 · 문자 찾기 JavaScript 풀이: 문자열 순회로 개수 세기
  11. 필수 학습 · 대문자 찾기 JavaScript 풀이
  12. 필수 학습 · 대문자로 통일 JavaScript 풀이
  13. 필수 학습 · 대소문자 변환 JavaScript 풀이
  14. 필수 학습 · 일곱 난쟁이 JavaScript 풀이: 두 명을 제외하는 완전탐색
  15. 필수 학습 · 코딩테스트 JS Map 풀이: 학급 회장 득표수 세기
  16. 필수 학습 · 코딩테스트 JS 스택 풀이: 올바른 괄호 검증하기
  17. 필수 학습 · 코딩테스트 JS 스택 풀이: 괄호문자 제거하기
  18. 필수 학습 · 코딩테스트 JS 스택 풀이: 크레인 인형뽑기 처리법
  19. 필수 학습 · 코딩테스트 JS 스택 풀이: 후위식 연산 계산하기
  20. 필수 학습 · 코딩테스트 JS 스택 풀이: 쇠막대기 레이저 절단 개수 세기
  21. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 두 정렬 배열 합치기
  22. 필수 학습 · 코딩테스트 JS 투 포인터 풀이: 공통 원소 추출하기
  23. 필수 학습 · 코딩테스트 JS 슬라이딩 윈도우 풀이: 최대 매출 구간 합 계산하기
  24. 필수 학습 · JavaScript 투 포인터: 합이 M인 연속 부분수열 개수
  25. 필수 학습 · 코딩테스트 JS 해시 풀이: 모든 아나그램 찾기
  26. 필수 학습 · 가장 긴 문자열 JavaScript 풀이
  27. 필수 학습 · 가운데 문자 출력 JavaScript 풀이
  28. 필수 학습 · 중복문자제거 JavaScript 풀이
  29. 필수 학습 · 코딩테스트 JS 고급: 최소 힙으로 다익스트라 최단 경로 구하기
  30. 필수 학습 · 코딩테스트 JS Union-Find: 연결 성분 수와 크기 구하기
  31. 필수 학습 · 코딩테스트 JS Trie: 접두사에 맞는 단어 수 세기
  32. 필수 학습 · 코딩테스트 JS Fenwick Tree: 값 갱신과 구간 합 처리
  33. 필수 학습 · 코딩테스트 JS 세그먼트 트리: 단일 대입과 구간 합
  34. 필수 학습 · 코딩테스트 JS LRU 캐시: 지도 타일 재사용 기록
  35. 필수 학습 · 코딩테스트 JS AVL 트리: 기준 이상 최솟값 찾기 현재 글
  36. 필수 학습 · 코딩테스트 JS 큐: 상담 창구 대기열 명령 처리
  37. 필수 학습 · 코딩테스트 JS 연결 리스트: 재생 대기 목록 관리
  38. 필수 학습 · JavaScript 원형 덱 연습: 최근 기록 창과 되돌리기
  39. 필수 학습 · JavaScript 해시 테이블 연습: 정규화 문자열 빈도와 등장 순서
  40. 필수 학습 · JavaScript 트리 순회 연습: 깊이별 노드 묶기
  41. 필수 학습 · JavaScript BST 연습: 닫힌 구간의 중복 키 보고서
  42. 필수 학습 · JavaScript 최소 힙 연습: 동률 순서를 지키는 작업 스케줄러
  43. 필수 학습 · JavaScript 그래프 연습: 연결 구역 크기를 작은 순서로 출력하기
  44. 필수 학습 · 중복단어제거 JavaScript 풀이
  45. 필수 학습 · TypeScript 이진 탐색 연습: 숫자 카드 존재 여부 확인
  46. 필수 학습 · 큰 수 출력하기 JavaScript 풀이
  47. 필수 학습 · 보이는 학생 JavaScript 풀이
  48. 필수 학습 · 가위바위보 JavaScript 풀이
  49. 필수 학습 · 점수계산 JavaScript 풀이

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기