JavaScript 반복형 세그먼트 트리: 구간 합·단일 대입·결합 순서

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

세그먼트 트리: 합을 묶어 두고 필요한 묶음만 고릅니다

작은 배열의 합계 트리로 세그먼트 트리를 배웁니다. 구간을 나누어 읽는 과정과 값 하나를 바꿀 때 고칠 부모를 단계별로 설명합니다.

먼저 작은 예제로 원리를 익히고, 마지막에 전체 구현을 펼쳐 보세요. 앞쪽 예제는 각각 독립적으로 브라우저 개발자 도구 Console에서 실행합니다. 같은 이름을 다시 선언했다는 오류가 나오면 새로고침 후 해당 예제를 실행하세요. 출력 뒤에 콘솔이 별도로 보여주는 undefined는 마지막 명령의 반환값일 수 있습니다.

세그먼트 트리의 개념을 표현한 표지 일러스트
주제를 표현한 개념 표지입니다. 정확한 동작과 값의 변화는 아래 코드와 표에서 확인하세요.

1. 네 전시실을 두 묶음으로 나눠 봅니다

전시실 네 곳의 예상 인원이 [4, 2, 7, 1]명입니다. 전체 합은 14명입니다. 한 전시실의 예상치가 바뀌고, 여러 구간의 합계를 계속 묻는 상황을 생각해 보세요. 매번 처음부터 전부 더하는 대신 작은 구간의 합을 미리 적어 두면 어떨까요?

먼저 이웃한 두 칸씩 묶습니다. 앞의 [4,2]는 6, 뒤의 [7,1]은 8입니다. 그 두 합을 다시 묶으면 14입니다. 큰 구간의 답을 두 작은 구간의 답으로 만드는 구조가 세그먼트 트리의 출발점입니다. 지금은 합계만 다루겠습니다.

왼쪽 묶음 오른쪽 묶음
원래 네 값 4, 2 7, 1
두 칸씩 합 4+2 = 6 7+1 = 8
전체 합 6+8 = 14 전체를 하나로 묶음

각 짧은 예제는 다른 블록 없이 실행됩니다. 전체를 브라우저 개발자 도구 Console에 붙여 넣거나 파일로 저장해 Node.js로 실행하세요. 콘솔에서 변수 이름 중복 오류가 나오면 새로고침 후 해당 블록을 실행합니다. 코드 바로 아래는 console.log의 출력입니다.

const values = [4, 2, 7, 1];
const left = values[0] + values[1];
const right = values[2] + values[3];
const total = left + right;
console.log(left);
console.log(right);
console.log(total);
6
8
14

left는 앞의 두 값, right는 뒤의 두 값을 더합니다. total은 그 두 결과만 더합니다. 출력 6,8,14는 표를 아래에서 위로 계산한 값입니다. 아직 특별한 클래스도 재귀도 필요하지 않습니다. “부모 칸의 합은 두 자식 칸의 합”이라는 연결만 기억하세요.

2. 묶음에 번호를 붙이면 배열에 저장할 수 있습니다

그림 대신 배열 하나에 묶음을 놓아 보겠습니다. 1번 칸을 전체 합으로 정하고 그 아래 두 묶음을 2번·3번에 둡니다. 실제 네 값은 맨 아래인 4~7번에 둡니다. 0번은 사용하지 않습니다. 여기의 번호는 원본 값의 배열 인덱스와 다른 저장 위치입니다.

tree의 칸 맡은 원본 인덱스 저장 값 계산 방법
1 0~3 전체 14 tree[2]+tree[3]
2 0~1 6 tree[4]+tree[5]
3 2~3 8 tree[6]+tree[7]
4 0 4 원본 첫 값
5 1 2 원본 두 번째 값
6 2 7 원본 세 번째 값
7 3 1 원본 네 번째 값

이 배치에서는 i번 칸의 두 자식이 i*2와 i*2+1입니다. 3번의 자식은 6번과 7번입니다. 외울 식으로 시작하지 말고 표의 1→2,3 / 2→4,5 / 3→6,7 관계를 먼저 확인하세요.

const values = [4, 2, 7, 1];
const tree = new Array(8).fill(0);
for (let i = 0; i < values.length; i += 1) {
  tree[4 + i] = values[i];
}
for (let i = 3; i >= 1; i -= 1) {
  tree[i] = tree[i * 2] + tree[i * 2 + 1];
}
console.log(tree.slice(1).join(", "));
14, 6, 8, 4, 2, 7, 1

첫 반복문은 원본 values[0]을 tree[4]에, values[1]을 tree[5]에 넣습니다. 4를 더하는 이유는 실제 값의 저장 시작점이 4번이기 때문입니다. 이렇게 실제 값을 놓은 맨 아래 칸들을 잎이라고 부릅니다.

다음 반복문은 3번, 2번, 1번 순서로 두 자식의 합을 계산합니다. 자식의 답이 준비된 다음 부모를 계산해야 합니다. 1번부터 계산하면 2번과 3번이 아직 초기값 0이어서 잘못된 전체 합을 얻게 됩니다. 출력에서는 사용하지 않는 0번을 slice(1)로 제외했습니다.

3. 한 값이 바뀌면 그 위의 묶음만 고칩니다

원본 인덱스 2의 값 7을 5로 교체하겠습니다. 그 값은 tree[6]입니다. 6번의 부모 3번은 5+1=6으로 바뀌고, 3번의 부모 1번은 6+6=12로 바뀝니다. 앞의 두 전시실을 맡은 2번은 이번 변경과 무관하므로 그대로 6입니다.

수정 순서 새 계산 결과
원본 인덱스 2에 해당하는 tree[6] 7 대신 5 대입 5
부모 tree[3] tree[6]+tree[7] = 5+1 6
그 부모 tree[1] tree[2]+tree[3] = 6+6 12
tree[2] 변경한 원소를 포함하지 않음 6 그대로

부모 번호는 자식 번호를 2로 나눈 몫입니다. 6의 부모는 3, 3의 부모는 1입니다. Math.floor는 나눗셈 결과의 소수 부분을 버리므로 Math.floor(3/2)는 1입니다. 이 경로만 따라 올라가면 됩니다.

const tree = [0, 14, 6, 8, 4, 2, 7, 1];
let position = 6;
tree[position] = 5;
while (position > 1) {
  position = Math.floor(position / 2);
  tree[position] = tree[position * 2] + tree[position * 2 + 1];
}
console.log(tree[3]);
console.log(tree[1]);
console.log(tree[5] + tree[3]);
6
12
8

tree[position] = 5가 교체입니다. 기존 7에 5를 더하는 코드가 아닙니다. 그다음 position을 부모 번호로 옮기고 부모의 두 자식을 다시 더합니다. while (position > 1)은 루트 1번을 계산한 뒤 더 올라가지 않게 합니다.

출력의 첫 두 값 6과 12는 바뀐 오른쪽 묶음과 전체 합입니다. 마지막 8은 원본 인덱스 1~3, 즉 [2,5,1]의 합입니다. tree[5]가 맡은 2와 tree[3]이 맡은 5+1을 합쳤습니다. 아래에서 왜 이 두 묶음을 고를 수 있는지 따로 보겠습니다.

4. 원하는 구간 안에 딱 들어오는 묶음만 고릅니다

갱신 전 [4,2,7,1]에서 인덱스 1부터 4 직전까지 더해 보세요. 필요한 값은 2,7,1입니다. 이 범위를 [1,4)라고 씁니다. 왼쪽 1은 포함하고 오른쪽 4는 포함하지 않는 표시입니다. 배열에는 4번 원소가 없지만 끝 경계로는 4를 쓸 수 있습니다.

고를 수 있는 묶음 원하는 범위에 맞는가? 선택
tree[1]: 인덱스 0~3 0번까지 포함하므로 너무 큼 고르지 않음
tree[2]: 인덱스 0~1 역시 0번이 섞임 더 작은 두 칸으로 나눔
tree[5]: 인덱스 1 정확히 범위 안 값 2를 선택
tree[3]: 인덱스 2~3 두 원소 모두 범위 안 합 8을 한 번에 선택

따라서 갱신 전 답은 tree[5]+tree[3]=2+8=10입니다. 범위에 일부만 걸치는 묶음은 쪼개고, 전부 들어오는 묶음은 저장한 답을 그대로 받습니다. 서로 겹치는 부모와 자식을 동시에 받지 않는 것도 중요합니다. 그래야 같은 전시실을 두 번 세지 않습니다.

갱신 전 배열에서 [0,2)의 합을 구하려고 tree[2]와 tree[4]를 더했습니다. 맞는 방법일까요?

답과 이유 확인하기

아닙니다. tree[2]만으로 이미 인덱스 0과 1의 합 6을 받았습니다. 여기에 tree[4]를 더하면 인덱스 0의 값 4를 두 번 셉니다. [0,2)는 tree[2] 하나면 충분합니다.

전체 구현은 위처럼 표를 보고 직접 고르는 대신, 구간의 왼쪽·오른쪽 경계를 잎의 번호로 옮겨 같은 선택을 반복합니다. 지금은 경계 인덱스 공식을 암기하기보다 “전부 들어오면 받고, 일부면 나눈다”를 먼저 설명할 수 있으면 됩니다.

5. 값이 다섯 개라면 남는 자리는 0으로 채웁니다

네 값의 다음 크기로 다섯 값을 저장하려면 맨 아래를 여덟 칸으로 잡습니다. 실제 값이 [4,2,7,1,3]이라면 [4,2,7,1,3,0,0,0]처럼 둡니다. 더할 때 0은 답을 바꾸지 않으므로 남은 자리를 채우기에 적합합니다. 원본 길이는 여전히 5이며 남은 세 칸을 새 전시실로 취급하지 않습니다.

맨 아래 값 그 위의 두 칸 합 네 칸 합 전체 합
4,2,7,1,3,0,0,0 6,8,3,0 14,3 17

전체 코드의 base는 맨 아래 값이 시작하는 번호이면서 그 층의 칸 수입니다. 네 값일 때 base=4, 다섯 값일 때 base=8입니다. 그래서 원본 index에 base를 더하면 대응하는 잎 번호가 됩니다. 아직 없는 칸을 왜 0으로 채우는지 이해했다면 뒤의 identity라는 이름은 “합계에서의 0 같은 기본값”으로 읽을 수 있습니다.

6. 전체 코드와 심화 설명을 읽는 순서

먼저 constructor에서 원본 값을 잎에 넣고 부모를 거꾸로 계산하는 부분을 찾으세요. 다음에는 set에서 값 하나를 교체하고 조상을 고치는 부분을 봅니다. 마지막으로 query가 범위에 맞는 묶음을 받는 방법을 읽습니다. 앞의 예제는 네 값과 정해진 질의만 직접 계산했으므로 어떤 구간에도 호출할 수 있는 완성 함수는 아닙니다.

아래 전체 구현은 숫자 덧셈 외의 결합도 받을 수 있도록 combine 함수를 사용합니다. 숫자 합계에서는 왼쪽과 오른쪽을 바꾸어도 답이 같지만 문자열은 “ab”+”cd”와 “cd”+”ab”가 다릅니다. 그래서 전체 query에는 왼쪽 결과와 오른쪽 결과를 따로 모으는 두 변수가 있습니다. 이 일반화와 결합 법칙은 기본 합계 과정을 이해한 뒤 심화에서 읽어도 됩니다.

set은 한 원소의 새 값을 대입하며, 구간 전체를 한 번에 바꾸는 기능은 없습니다. 작은 정수 합계를 사용한 앞의 예제와 달리 범용 코드를 다른 값에 적용하려면 결합 연산과 숫자 범위의 약속도 지켜야 합니다. 먼저 같은 [4,2,7,1]을 전체 코드에 넣고 query(1,4)가 10, set(2,5) 뒤에는 8이 되는지 연결해 보세요.

관련 코딩테스트 문제로 이어서 연습합니다

이 자료구조의 블로그 연습 문제와 해설에서 배운 동작을 적용해 보세요. 먼저 작은 예시를 직접 처리한 뒤 입력 전체를 다루는 코드로 확장하면 됩니다. 아래 전체 구현은 연결된 문제의 기존 메서드와 반환 형식을 유지합니다.

전체 구현 · 상세 설명 · 예외와 성능 분석 펼치기

여기부터는 필요한 기능을 골라 읽는 참고 영역입니다. 새로운 파일로 전체 코드를 실행할 때는 아래 Node.js 실행 안내를 따르세요. 앞쪽의 작은 브라우저 실습과 전체 파일을 한 콘솔에 이어 붙이지 마세요. 기능별 입력 조건과 반환 형식은 아래 설명을 기준으로 합니다.

문제 상황과 API 계약

대시보드에서 구간별 합계를 조회하는 동시에 특정 칸의 값을 새 값으로 교체한다고 합시다. 접두사 합 배열은 한 칸 수정에 뒤쪽 전체를 다시 계산해야 합니다. 세그먼트 트리는 변경한 잎에서 루트까지의 조상만 다시 계산합니다. 합계 외의 결합 연산에도 같은 구간 분해를 재사용한다는 점이 Fenwick Tree와의 선택 기준입니다.

query(left,right)는 [left,right)의 결합 결과이고 set(index,value)는 값을 교체합니다. add가 아닙니다. 기본 combine은 덧셈, identity는 0입니다. 빈 배열과 빈 구간도 허용하며 query(i,i)는 identity를 반환합니다. 배열 길이는 교육용으로 1,000,000 이하로 제한합니다.

일반화하려면 값 집합과 combine, identity가 모노이드를 이뤄야 합니다. 결합 법칙 op(op(a,b),c)=op(a,op(b,c))와 양쪽 항등원 op(e,a)=op(a,e)=a가 필요합니다. 교환 법칙은 필요하지 않습니다. 정수 합계에서는 e=0이고 문자열 연결에서는 e=""입니다. 결합 함수는 인수나 기존 노드를 수정하지 않는 순수 함수여야 합니다.

정확성을 지키는 불변식

base는 n 이상인 가장 작은 2의 거듭제곱입니다. 원본 values[i]는 tree[base+i]에 놓고 나머지 잎은 항등원으로 채웁니다. 각 내부 노드는 왼쪽 자식과 오른쪽 자식을 그 순서로 결합합니다. 따라서 루트는 패딩을 포함해도 실제 배열 전체와 같은 결과를 갖습니다.

조회는 l,r을 잎 위치로 옮긴 뒤 아직 처리하지 않은 [l,r) 영역을 좁힙니다. l이 오른쪽 자식이면 왼쪽 형제가 범위 밖이므로 tree[l]만 받아 l을 하나 늘립니다. r이 홀수이면 r−1이 범위 안의 마지막 왼쪽 자식이므로 먼저 r을 줄이고 그 노드를 받습니다. 나머지는 완전한 형제 쌍이라 부모로 올릴 수 있습니다.

왼쪽에서 고른 블록은 accLeft 뒤에, 오른쪽에서 고른 블록은 accRight 앞에 붙입니다. 둘 다 뒤에 붙이면 합에서는 버그가 숨지만 문자열 연결이나 행렬 곱에서는 순서가 틀어집니다. 반복이 끝나면 미처리 구간이 없으므로 combine(accLeft,accRight)가 원래 순서의 전체 답입니다.

전체 구현과 실행

Node.js 24의 CommonJS 환경을 기준으로 합니다. 아래 전체 코드를 segment-tree.cjs로 저장하고 파일이 있는 폴더에서 node segment-tree.cjs를 실행하세요. 외부 패키지는 필요하지 않습니다. module.exports는 다른 파일에서 가져올 때 사용하며 require.main 조건 안의 호출은 직접 실행할 때만 작동합니다.

class SegmentTree {
  constructor(values, combine = (a, b) => a + b, identity = 0) {
    if (!Array.isArray(values) || values.length > 1_000_000) {
      throw new RangeError('길이 1,000,000 이하 배열이 필요합니다.');
    }
    if (typeof combine !== 'function') throw new TypeError('결합 함수가 필요합니다.');
    this.n = values.length;
    this.combine = combine;
    this.identity = identity;
    this.base = 1;
    while (this.base < this.n) this.base *= 2;
    this.tree = Array(this.base * 2).fill(identity);
    for (let i = 0; i < this.n; i++) this.tree[this.base + i] = values[i];
    for (let i = this.base - 1; i > 0; i--) {
      this.tree[i] = combine(this.tree[i * 2], this.tree[i * 2 + 1]);
    }
  }
  boundary(i) {
    if (!Number.isInteger(i) || i < 0 || i > this.n) throw new RangeError('잘못된 경계입니다.');
  }
  set(index, value) {
    this.boundary(index);
    if (index === this.n) throw new RangeError('갱신 인덱스는 n보다 작아야 합니다.');
    let p = this.base + index;
    this.tree[p] = value;
    while (p > 1) {
      p = Math.floor(p / 2);
      this.tree[p] = this.combine(this.tree[p * 2], this.tree[p * 2 + 1]);
    }
  }
  query(left, right) {
    this.boundary(left);
    this.boundary(right);
    if (left > right) throw new RangeError('left는 right 이하여야 합니다.');
    let l = left + this.base, r = right + this.base;
    let accLeft = this.identity, accRight = this.identity;
    while (l < r) {
      if (l % 2 === 1) accLeft = this.combine(accLeft, this.tree[l++]);
      if (r % 2 === 1) accRight = this.combine(this.tree[--r], accRight);
      l = Math.floor(l / 2);
      r = Math.floor(r / 2);
    }
    return this.combine(accLeft, accRight);
  }
}

module.exports = { SegmentTree };

if (require.main === module) {
  const s = new SegmentTree([4, 2, 7, 1, 3]);
  console.log(s.query(1, 5));
  s.set(2, 5);
  console.log(s.query(1, 5));
}

직접 실행 출력:

13
11

생성자는 잎을 채운 뒤 base−1부터 거꾸로 계산합니다. 두 자식의 결과가 먼저 존재하도록 순서를 잡았으므로 재귀 빌드가 필요 없습니다. set도 잎에 대입한 다음 Math.floor(p/2)로 올라가 두 자식을 다시 결합합니다.

인덱스에는 비트 시프트 대신 나머지와 나눗셈을 써서 32비트 변환을 피했습니다. 그래도 JavaScript 배열과 메모리는 무한하지 않으므로 길이 제한은 유지합니다. 패딩은 identity로 채웠고 fill이 같은 객체 참조를 반복할 수 있으므로 객체 항등원을 쓸 때도 combine이 수정하지 않아야 합니다.

예제는 [1,5) 합 13을 구하고 2번 값을 7에서 5로 대입한 뒤 11을 구합니다. 문자열 버전 new SegmentTree(["a","b","c","d","e"], (a,b)=>a+b, "")에서 query(1,5)는 "bcde"여야 합니다. 이 순서 확인은 합계만 테스트할 때 놓치는 결함을 찾습니다.

상태 변화 따라가기

[4,2,7,1,3], query(1,5) l / r accLeft / accRight 행동
시작, base=8 9 / 13 0 / 0 원본 구간 [1,5)
홀수 경계 처리 10 / 12 2 / 3 tree[9]=2, tree[12]=3
부모로 이동 5 / 6 2 / 3 미처리 내부 구간 [5,6)
왼쪽 홀수 처리 6 / 6 10 / 3 tree[5]=7+1을 받음
종료 3 / 3 10 / 3 10+3=13
set(2,5) 뒤 같은 조회 같은 경로 8 / 3 잎 10 및 조상 5,2,1 갱신 → 11

복잡도와 적용하지 말아야 할 경우

작업 최악 시간 공간 / 전제
빌드 O(n+1) base<2n (n>0), 저장 O(n+1)
단일 set / query O(log(n+1)) combine이 O(1)일 때; 상환 주장 아님
한 작업 추가 공간 O(1) 반복형 인덱스와 누산기
combine이 O(T)일 때 해당 시간에 T를 곱함 문자열 연결은 길이에 따른 비용 별도

JavaScript 부동소수 덧셈은 일반적으로 결합 법칙이 성립하지 않습니다. 이 합계 예제는 안전한 정수와 모든 부분합의 정확성을 전제로 합니다. 충분조건은 현재 원소들의 절댓값 합이 Number.MAX_SAFE_INTEGER 이하인 것입니다. 소수 금액을 그대로 더해 회계 정확성을 기대하면 안 됩니다.

덧셈만 필요하고 갱신이 증가량으로 주어진다면 Fenwick Tree가 더 작고 단순할 수 있습니다. 갱신이 전혀 없다면 정적 접두사 합이 질의 O(1)로 더 효율적입니다. 이 구조의 일반성 때문에 모든 구간 문제에서 항상 유리한 것은 아닙니다.

구간 전체에 값을 더하는 작업을 set 반복으로 처리하면 원소 수만큼 비용이 늘어납니다. Lazy propagation은 별도의 구현이며 이 코드에는 없습니다. 순서 의존 연산, 큰 문자열, 변경 가능한 객체를 결합할 때는 결합 함수의 비용과 불변성까지 계약에 포함해야 합니다.

연습으로 확인하기

구간 합을 유지하면서 특정 전시실의 예상 인원을 새 값으로 바꿔 보세요. 증가와 대입을 혼동하지 않는지, 오른쪽 경계를 포함하지 않는지 확인합니다.

[창작 문제] 전시실 예상 인원판 교체

LeetCode 307 – Range Sum Query – Mutable — 값 갱신과 구간 합을 연습하는 공식 문제입니다. 원문은 양 끝 포함 구간을 사용하므로 이 글의 반열린 API로 옮길 때 오른쪽 경계를 변환해야 합니다.

공식 자료

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

직접 실습: 연산 비용으로 구조를 설명합니다

실습 주제: JavaScript 반복형 세그먼트 트리: 구간 합·단일 대입·결합 순서

  1. 본문 구현에서 저장되는 값과 연결 관계를 그림으로 적습니다.
  2. 조회·삽입·삭제 중 이 구조가 가장 자주 수행할 연산을 고릅니다.
  3. 연산 전후에도 유지되어야 하는 규칙을 한 문장으로 적습니다.
  4. 배열이나 Map 같은 다른 구조로 바꿨을 때 시간·공간 비용을 비교합니다.
풀이 기준과 확인 결과

메서드 이름만 외우지 말고 한 번의 연산에서 어떤 값과 연결이 바뀌는지 추적하세요. 빈 구조, 원소 한 개, 중복값, 연속 삽입·삭제를 실행했을 때 본문이 설명한 불변식이 유지되면 성공입니다.

테스트 체크리스트

  • 빈 구조에 대한 조회·삭제 처리
  • 첫 원소와 마지막 원소 변경
  • 중복값 또는 동일 우선순위 처리
  • 입력 크기가 커졌을 때 예상 복잡도 유지

이 글이 도움이 되었나요?

조회 중

자료구조 학습 순서

필수 18개 · 전체 18개

읽음 기록 관리

전체 과정 목차 (18개)
  1. 필수 학습 · 자료구조 선택 가이드: 연산 비용으로 배열·스택·큐·Set 고르기
  2. 필수 학습 · JavaScript 배열: 인덱스 조회와 삽입·삭제 비용
  3. 필수 학습 · JavaScript Map·Set: 값 조회와 중복 제거 실습
  4. 필수 학습 · 자료구조 스택 쉽게 이해하기: push pop으로 문제 풀이 감 잡기
  5. 필수 학습 · 큐와 FIFO: head 인덱스로 JavaScript 대기열 만들기
  6. 필수 학습 · 단방향 연결 리스트: head·tail 삽입과 삭제
  7. 필수 학습 · JavaScript 원형 덱 구현: 양끝 삽입·삭제와 고정 용량 버퍼
  8. 필수 학습 · JavaScript 문자열 해시 테이블 구현: 충돌 처리와 리사이즈, NFC 정규화
  9. 필수 학습 · 트리 자료구조 차이: 이진 트리 BST MST 구분하기
  10. 필수 학습 · JavaScript 이진 탐색 트리 구현: 중복 키와 세 가지 삭제 처리
  11. 필수 학습 · JavaScript 최소 힙 구현: 우선순위 큐의 push·pop과 비교 함수
  12. 필수 학습 · JavaScript 그래프 구현: 인접 리스트·인접 행렬 비교와 BFS
  13. 필수 학습 · JavaScript Union-Find: 경로 압축과 크기 합치기로 연결 상태 관리하기
  14. 필수 학습 · JavaScript Trie: Unicode 접두사 검색과 안전한 삭제 구현
  15. 필수 학습 · JavaScript Fenwick Tree: lowbit로 구간 합과 단일 증가 갱신 구현
  16. 필수 학습 · JavaScript 반복형 세그먼트 트리: 구간 합·단일 대입·결합 순서 현재 글
  17. 필수 학습 · JavaScript LRU 캐시: Map과 이중 연결 리스트의 불변식
  18. 필수 학습 · JavaScript AVL 트리: 높이 불변식과 LL·RR·LR·RL 삽입 회전

새 글 받아보기

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

RSS 피드 구독하기

댓글 남기기