JavaScript BST 연습: 닫힌 구간의 중복 키 보고서

2026.09.10·수정 2026.09.13·약 9분·작성: 해비·블로그 소개
이진 탐색 트리의 핵심 구조를 표현한 개념 표지
구조의 특징을 표현한 개념 이미지입니다. 정확한 동작은 아래 코드와 추적 표를 함께 확인하세요.

핵심 요약

중복 횟수를 가진 BST에서 닫힌 구간의 키만 오름차순으로 출력합니다. 모든 노드를 수집해 정렬하는 대신 BST의 순서로 불가능한 가지를 제외하는 독립 창작 문제입니다.

독립 창작 문제

이 문제의 상황·입출력 계약·예시는 이 글을 위해 직접 구성했습니다. 공식 문제를 번역하거나 복제한 지문이 아닙니다. 구현 개념은 JavaScript 이진 탐색 트리 구현: 중복 키와 세 가지 삭제 처리에서 먼저 확인할 수 있습니다.

이미 만들어진 이진 탐색 트리와 low, high가 주어집니다. low ≤ key ≤ high인 키를 중복 횟수 count만큼 오름차순으로 출력하세요. low가 high보다 크면 빈 배열을 반환합니다. 트리를 새로 구축하거나 변경할 필요는 없습니다.

각 노드의 왼쪽 부분 트리에는 더 작은 키, 오른쪽에는 더 큰 키만 존재합니다. 중복 키는 별도 노드로 나뉘지 않고 해당 노드의 count에 합쳐져 있습니다. 단순 이진 트리를 BST라고 가정하면 가지치기가 유효하지 않으므로 이 조건은 입력 계약의 핵심입니다.

입력·출력과 제약

표준 입력은 {root,low,high} JSON 객체입니다. root는 null 또는 {key,count,left,right} 노드입니다. key, low, high는 안전 정수이고 count는 필수 필드인 1~100의 정수입니다. 없는 자식은 null 또는 필드 생략입니다. 서로 다른 노드 수 U는 0~10000, count 합 N은 100000 이하, 최대 깊이는 200입니다.

입력은 엄격한 BST이며 공유 노드나 사이클이 없습니다. 정답 함수는 트리 전체의 유효성 검사를 다시 하지 않습니다. 표준 출력은 조건을 만족하는 숫자 배열이고 양쪽 경계와 같은 키도 포함합니다. 출력 개수가 커질 수 있으므로 count를 생략하거나 1로 추측해서 처리하지 않습니다.

입력 예시

{"root":{"key":12,"count":1,"left":{"key":7,"count":2,"left":null,"right":null},"right":{"key":18,"count":1,"left":{"key":14,"count":3,"left":null,"right":null},"right":null}},"low":7,"high":14}

출력 예시

[7,7,12,14,14,14]

실행 방법

Node.js CommonJS 환경에서 정답의 전체 코드를 bst-problem.cjs로 저장합니다. 입력 예시를 UTF-8 input.json 파일로 저장한 뒤 Windows PowerShell에서는 아래 명령을 실행하세요. 내장 node:fs 외에 다른 파일이나 패키지가 필요하지 않습니다.

Get-Content -Raw -Encoding UTF8 ./input.json | node ./bst-problem.cjs

macOS·Linux의 Bash에서는 아래 입력 리다이렉션을 사용할 수 있습니다. 이 < 문법은 PowerShell 명령이 아닙니다.

node ./bst-problem.cjs < input.json

힌트

현재 키가 low보다 작으면 왼쪽 전체도 작습니다. 반대로 중위 순회에서 꺼낸 키가 high를 넘으면 그 뒤에 나올 값은 전부 범위 밖입니다. 중위 순서를 유지하는 스택과 이 두 가지 생략 조건을 결합하세요.

정답 코드와 해설 펼치기

한 파일로 실행하는 전체 정답

'use strict';

function checkKey(key) {
  if (!Number.isSafeInteger(key)) throw new TypeError('key must be a safe integer');
}

function rangeValues(root, low, high) {
  checkKey(low);
  checkKey(high);
  const result = [];
  if (low > high) return result;
  const stack = [];
  let current = root;
  while (current != null || stack.length > 0) {
    while (current != null) {
      if (current.key < low) {
        current = current.right;
      } else {
        stack.push(current);
        current = current.left;
      }
    }
    if (stack.length === 0) break;
    current = stack.pop();
    if (current.key > high) break;
    for (let copy = 0; copy < current.count; copy += 1) {
      result.push(current.key);
    }
    current = current.right;
  }
  return result;
}


function solve({ root, low, high }) {
  return rangeValues(root, low, high);
}

module.exports = { solve };

if (require.main === module) {
  const fs = require('node:fs');
  const input = JSON.parse(fs.readFileSync(0, 'utf8').replace(/^\uFEFF/, ''));
  process.stdout.write(JSON.stringify(solve(input)) + '\n');
}

정답 해설: 순서가 생략을 정당화합니다

내부 반복에서 key < low인 노드는 스택에 저장하지 않고 오른쪽으로 넘어갑니다. 그 노드의 왼쪽까지 방문할 필요가 없기 때문입니다. 나머지는 스택에 쌓고 왼쪽으로 내려가 아직 출력하지 않은 작은 키를 먼저 처리합니다.

스택에서 꺼낸 키는 low 이상입니다. 그 키가 high를 넘으면 이후 중위 방문도 더 큰 키뿐이라 break로 전체 반복을 끝낼 수 있습니다. 범위 안이면 count만큼 결과에 추가하고 오른쪽 부분 트리로 이동합니다. 입력의 연결이나 count를 고치지 않는 읽기 전용 연산입니다.

예시 단계 판정 출력
루트 12 보류, 왼쪽 7 7은 low와 같음 7,7
스택의 12 처리 범위 안 7,7,12
오른쪽 18 보류, 왼쪽 14 14는 high와 같음 7,7,12,14,14,14
18 처리 시도 high 초과, 종료 변화 없음

예시에서는 왼쪽 키 7의 count가 2이고 키 14의 count가 3입니다. 같은 숫자를 여러 번 출력하는 것은 중복 방문 오류가 아니라 문제에서 요구한 확장입니다. low=high=14라면 14 세 개만 출력하고 low=15,high=14라면 탐색 없이 빈 결과입니다.

복잡도

h는 높이, k는 실제 출력 개수입니다. 유효한 BST에서 범위 안 노드와 두 경계로 가는 경로만 방문하므로 시간은 O(h+k+1), 최악 O(U+k)입니다. 보조 스택은 O(h+1), 결과는 O(k)입니다. 표준 입력 JSON 트리까지 포함한 전체 프로그램 공간은 O(U+k)이며, 트리 균형은 보장하지 않으므로 h를 무조건 log U로 바꾸면 안 됩니다.

이 문제는 완성된 트리를 받으므로 삽입으로 구축하는 비용은 없습니다. values 배열을 BST에 하나씩 넣는 별도 입력 설계를 택하면 정렬된 입력에서 구축만 O(U²)이 될 수 있습니다. 입력 형식이 달라지면 같은 범위 함수라도 프로그램 전체 복잡도가 달라집니다.

직접 확인할 경계

빈 root, low > high, 모든 키보다 낮거나 높은 범위, 한 키만 포함하는 범위, 중복 횟수가 큰 노드, 편향 트리를 확인하세요. 테스트에서는 BST에 넣은 원래 다중집합을 filter 후 sort한 결과와 비교하면 가지치기 구현과 독립적으로 정답을 확인할 수 있습니다.

함께 연습하고 확인할 자료

Delete Node in a BST: BST 노드 삭제를 연습하는 외부 문제입니다. 이 글의 count 중복 정책과 달리 공식 입력의 키 조건을 따르세요.

관련 개념 참고: Princeton Algorithms — Binary Search Trees 공식 자료와 문제 페이지는 2026년 9월 10일 확인했습니다. 외부 문제의 지문·예시를 복제하지 않고 이 글의 예제와 창작 문제를 별도로 구성했습니다.

풀이 전에 확인할 순서

  1. 입력값과 출력값을 한 문장으로 다시 적습니다.
  2. 반복할 대상과 비교·저장할 값을 정합니다.
  3. 필요한 자료구조와 시간복잡도를 예상합니다.
  4. 코드를 보기 전에 손으로 작은 예제를 계산합니다.
힌트: JavaScript BST 연습: 닫힌 구간의 중복 키 보고서

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

테스트 확인

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

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

이 글이 도움이 되었나요?

조회 중

코딩테스트 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 피드 구독하기

댓글 남기기