Below is a small integer sequence. The demo builds a wavelet tree over it and lets you ask for the k-th smallest value in any range [l, r]. Each step shows which sub-tree the algorithm descends into and why.
<p class="hint">The sequence below is indexed by a wavelet tree. Pick a range <b>[l, r]</b> and a rank <b>k</b>, then press <b>Find k-th Smallest</b> to see the algorithm descend the tree level by level.</p>
<div class="seq-row" id="seqRow"></div>
<div class="controls">
<label>l (1-based): <input id="inL" type="number" min="1" max="16" value="2"></label>
<label>r: <input id="inR" type="number" min="1" max="16" value="10"></label>
<label>k: <input id="inK" type="number" min="1" max="16" value="3"></label>
<button id="btnFind" type="button">Find k-th Smallest</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="treeArea"></div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.seq-row { display: flex; gap: 3px; flex-wrap: wrap; margin-bottom: .6rem; }
.seq-cell { width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 6px;
background: #e8eef3; border: 1px solid #cdd9e3; color: #1d3557; }
.seq-cell.in-range { background: #b8d4ec; border-color: #6fa8c8; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .6rem; }
label { font-size: .85rem; color: #444; }
input[type=number] { width: 48px; margin-left: 4px; padding: 3px 5px; border: 1px solid #cdd9e3;
border-radius: 5px; font-size: .85rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
#treeArea { margin-top: .3rem; }
.level-label { font: 600 12px system-ui; color: #666; margin: .4rem 0 .15rem; }
.level-row { display: flex; gap: 3px; flex-wrap: wrap; }
.bit-cell { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
font: 700 11px ui-monospace, monospace; border-radius: 4px;
background: #f0f0f0; border: 1px solid #d0d0d0; color: #555; }
.bit-cell.lo { background: #dbeafe; border-color: #93c5fd; color: #1e40af; }
.bit-cell.hi { background: #fce7f3; border-color: #f9a8d4; color: #9d174d; }
.bit-cell.active { outline: 2px solid #f97316; outline-offset: 1px; }
.sep { width: 8px; }
.result { margin-top: .6rem; font: 700 1rem system-ui; min-height: 1.4em; }
.result.ok { color: #0a7d33; }
.result.err { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Wavelet tree over a small sequence of integers.
// Supports k-th smallest in range [l,r] in O(log sigma) steps.
// Each node stores a bitvector: 0 = symbol goes to lo child, 1 = hi child.
// To find the k-th smallest in [l,r]:
// 1. Count zeros in [l,r] of the current node's bitvector => lo_count.
// 2. If k <= lo_count, recurse into lo child with the mapped range and k.
// 3. Otherwise recurse into hi child with the mapped range and k - lo_count.
// 4. At a leaf (range of size 1), the answer is that single symbol.
const SEQ = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 2];
const N = SEQ.length; // 16
// Build wavelet tree as array of levels.
// Each level is an array of 0/1 bits (same length as SEQ).
// Parallel: also track the alphabet range [lo, hi] at each level.
// We split [lo, hi] into [lo, mid] and [mid+1, hi].
// The level bitvector: 0 if seq[i] <= mid, else 1.
// After splitting, the order of elements within each half is preserved.
function buildLevels(seq, lo, hi) {
if (lo >= hi) return [];
const mid = Math.floor((lo + hi) / 2);
const bits = seq.map(v => v <= mid ? 0 : 1);
const loSeq = seq.filter(v => v <= mid);
const hiSeq = seq.filter(v => v > mid);
return [{ bits, lo, hi, mid }].concat(
buildLevels(loSeq, lo, mid),
buildLevels(hiSeq, mid + 1, hi)
);
}
// Build a proper tree structure for navigation.
// node = { lo, hi, mid, bits, left, right }
function buildTree(seq, lo, hi) {
if (lo >= hi) return { lo, hi, leaf: true, value: lo };
const mid = Math.floor((lo + hi) / 2);
const bits = seq.map(v => v <= mid ? 0 : 1);
const loSeq = seq.filter(v => v <= mid);
const hiSeq = seq.filter(v => v > mid);
return {
lo, hi, mid, bits, seq,
left: buildTree(loSeq, lo, mid),
right: buildTree(hiSeq, mid + 1, hi),
leaf: false
};
}
const ROOT = buildTree(SEQ, 1, 9);
// Count zeros in bits[l..r] (0-indexed, inclusive)
function countZeros(bits, l, r) {
let c = 0;
for (let i = l; i <= r; i++) if (bits[i] === 0) c++;
return c;
}
// Map position l (0-indexed) in parent seq to child (lo or hi) index.
// lo child: rank of zeros up to l-1 (number of 0s before position l)
function mapToLo(bits, pos) {
let c = 0;
for (let i = 0; i < pos; i++) if (bits[i] === 0) c++;
return c;
}
function mapToHi(bits, pos) {
let c = 0;
for (let i = 0; i < pos; i++) if (bits[i] === 1) c++;
return c;
}
// k-th smallest (1-indexed k) in node.seq[l..r] (0-indexed, inclusive)
// Returns { value, steps } where steps is array of { node, l, r, k, dir }
function kthSmallest(node, l, r, k) {
const steps = [];
function go(nd, l, r, k) {
if (nd.leaf) {
steps.push({ node: nd, l, r, k, dir: 'leaf' });
return nd.value;
}
const zeros = countZeros(nd.bits, l, r);
const dir = k <= zeros ? 'lo' : 'hi';
steps.push({ node: nd, l, r, k, zeros, dir });
if (dir === 'lo') {
const nl = mapToLo(nd.bits, l);
const nr = nl + zeros - 1;
return go(nd.left, nl, nr, k);
} else {
const ones_before_l = mapToHi(nd.bits, l);
const nl = ones_before_l;
const nr = nl + (r - l + 1 - zeros) - 1;
return go(nd.right, nl, nr, k - zeros);
}
}
const value = go(node, l, r, k);
return { value, steps };
}
// Render the original sequence with range highlighted
function renderSeq(l, r) {
const row = document.getElementById('seqRow');
row.innerHTML = '';
SEQ.forEach((v, i) => {
const d = document.createElement('div');
d.className = 'seq-cell' + (i >= l && i <= r ? ' in-range' : '');
d.textContent = v;
row.appendChild(d);
});
}
// Render tree levels with bits highlighted for active range
function renderTree(steps) {
const area = document.getElementById('treeArea');
area.innerHTML = '';
if (!steps || steps.length === 0) return;
steps.forEach((step, si) => {
const nd = step.node;
if (nd.leaf) {
const lbl = document.createElement('div');
lbl.className = 'level-label';
lbl.textContent = fmt('Step %si%%v%: leaf → answer = %value%', { si: si, v: 1, value: nd.value });
area.appendChild(lbl);
return;
}
const lbl = document.createElement('div');
lbl.className = 'level-label';
const info = step.dir === 'lo'
? fmt('zeros in [%l%,%r%] = %zeros% ≥ k=%k% → go LEFT (≤%mid%)', { l: step.l, r: step.r, zeros: step.zeros, k: step.k, mid: nd.mid })
: fmt('zeros in [%l%,%r%] = %zeros% < k=%k% → go RIGHT (>%mid%)', { l: step.l, r: step.r, zeros: step.zeros, k: step.k, mid: nd.mid });
lbl.textContent = fmt('Step %si%%v% [%lo%..%hi%]: %info%', { si: si, v: 1, lo: nd.lo, hi: nd.hi, info: info });
area.appendChild(lbl);
const row = document.createElement('div');
row.className = 'level-row';
nd.bits.forEach((b, i) => {
if (i > 0 && i === step.l) {
const sep = document.createElement('div'); sep.className = 'sep'; row.appendChild(sep);
}
const d = document.createElement('div');
const inRange = i >= step.l && i <= step.r;
d.className = 'bit-cell ' + (b === 0 ? 'lo' : 'hi') + (inRange ? ' active' : '');
d.textContent = b;
row.appendChild(d);
if (i === step.r && step.r < nd.bits.length - 1) {
const sep = document.createElement('div'); sep.className = 'sep'; row.appendChild(sep);
}
});
area.appendChild(row);
});
}
function run() {
const l0 = parseInt(document.getElementById('inL').value, 10) - 1; // 0-indexed
const r0 = parseInt(document.getElementById('inR').value, 10) - 1;
const k = parseInt(document.getElementById('inK').value, 10);
const res = document.getElementById('result');
if (isNaN(l0) || isNaN(r0) || isNaN(k) || l0 < 0 || r0 >= N || l0 > r0 || k < 1 || k > r0 - l0 + 1) {
res.textContent = '⚠ Invalid input. Check that 1 ≤ l ≤ r ≤ 16 and 1 ≤ k ≤ (r−l+1).';
res.className = 'result err';
renderSeq(-1, -1); renderTree(null);
return;
}
renderSeq(l0, r0);
const { value, steps } = kthSmallest(ROOT, l0, r0, k);
renderTree(steps);
res.textContent = fmt('The %k%-th smallest value in positions [%l0%%v%, %r0%%v%] is %value%.', { k: k, l0: l0, v: 1, r0: r0, value: value });
res.className = 'result ok';
console.log('k-th smallest answer:', value, '| steps:', steps.length);
}
function reset() {
document.getElementById('inL').value = 2;
document.getElementById('inR').value = 10;
document.getElementById('inK').value = 3;
document.getElementById('treeArea').innerHTML = '';
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
renderSeq(-1, -1);
}
document.getElementById('btnFind').addEventListener('click', run);
document.getElementById('btnReset').addEventListener('click', reset);
renderSeq(-1, -1);
<p class="hint">The sequence below is indexed by a wavelet tree. Pick a range <b>[l, r]</b> and a rank <b>k</b>, then press <b>Find k-th Smallest</b> to see the algorithm descend the tree level by level.</p>
<div class="seq-row" id="seqRow"></div>
<div class="controls">
<label>l (1-based): <input id="inL" type="number" min="1" max="16" value="2"></label>
<label>r: <input id="inR" type="number" min="1" max="16" value="10"></label>
<label>k: <input id="inK" type="number" min="1" max="16" value="3"></label>
<button id="btnFind" type="button">Find k-th Smallest</button>
<button id="btnReset" type="button" class="ghost">Reset</button>
</div>
<div id="treeArea"></div>
<div class="result" id="result"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.seq-row { display: flex; gap: 3px; flex-wrap: wrap; margin-bottom: .6rem; }
.seq-cell { width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; border-radius: 6px;
background: #e8eef3; border: 1px solid #cdd9e3; color: #1d3557; }
.seq-cell.in-range { background: #b8d4ec; border-color: #6fa8c8; }
.controls { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin-bottom: .6rem; }
label { font-size: .85rem; color: #444; }
input[type=number] { width: 48px; margin-left: 4px; padding: 3px 5px; border: 1px solid #cdd9e3;
border-radius: 5px; font-size: .85rem; }
button { font: 600 13px system-ui, sans-serif; padding: .4rem .8rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
#treeArea { margin-top: .3rem; }
.level-label { font: 600 12px system-ui; color: #666; margin: .4rem 0 .15rem; }
.level-row { display: flex; gap: 3px; flex-wrap: wrap; }
.bit-cell { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
font: 700 11px ui-monospace, monospace; border-radius: 4px;
background: #f0f0f0; border: 1px solid #d0d0d0; color: #555; }
.bit-cell.lo { background: #dbeafe; border-color: #93c5fd; color: #1e40af; }
.bit-cell.hi { background: #fce7f3; border-color: #f9a8d4; color: #9d174d; }
.bit-cell.active { outline: 2px solid #f97316; outline-offset: 1px; }
.sep { width: 8px; }
.result { margin-top: .6rem; font: 700 1rem system-ui; min-height: 1.4em; }
.result.ok { color: #0a7d33; }
.result.err { color: #c92f3c; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Wavelet tree over a small sequence of integers.
// Supports k-th smallest in range [l,r] in O(log sigma) steps.
// Each node stores a bitvector: 0 = symbol goes to lo child, 1 = hi child.
// To find the k-th smallest in [l,r]:
// 1. Count zeros in [l,r] of the current node's bitvector => lo_count.
// 2. If k <= lo_count, recurse into lo child with the mapped range and k.
// 3. Otherwise recurse into hi child with the mapped range and k - lo_count.
// 4. At a leaf (range of size 1), the answer is that single symbol.
const SEQ = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 2];
const N = SEQ.length; // 16
// Build wavelet tree as array of levels.
// Each level is an array of 0/1 bits (same length as SEQ).
// Parallel: also track the alphabet range [lo, hi] at each level.
// We split [lo, hi] into [lo, mid] and [mid+1, hi].
// The level bitvector: 0 if seq[i] <= mid, else 1.
// After splitting, the order of elements within each half is preserved.
function buildLevels(seq, lo, hi) {
if (lo >= hi) return [];
const mid = Math.floor((lo + hi) / 2);
const bits = seq.map(v => v <= mid ? 0 : 1);
const loSeq = seq.filter(v => v <= mid);
const hiSeq = seq.filter(v => v > mid);
return [{ bits, lo, hi, mid }].concat(
buildLevels(loSeq, lo, mid),
buildLevels(hiSeq, mid + 1, hi)
);
}
// Build a proper tree structure for navigation.
// node = { lo, hi, mid, bits, left, right }
function buildTree(seq, lo, hi) {
if (lo >= hi) return { lo, hi, leaf: true, value: lo };
const mid = Math.floor((lo + hi) / 2);
const bits = seq.map(v => v <= mid ? 0 : 1);
const loSeq = seq.filter(v => v <= mid);
const hiSeq = seq.filter(v => v > mid);
return {
lo, hi, mid, bits, seq,
left: buildTree(loSeq, lo, mid),
right: buildTree(hiSeq, mid + 1, hi),
leaf: false
};
}
const ROOT = buildTree(SEQ, 1, 9);
// Count zeros in bits[l..r] (0-indexed, inclusive)
function countZeros(bits, l, r) {
let c = 0;
for (let i = l; i <= r; i++) if (bits[i] === 0) c++;
return c;
}
// Map position l (0-indexed) in parent seq to child (lo or hi) index.
// lo child: rank of zeros up to l-1 (number of 0s before position l)
function mapToLo(bits, pos) {
let c = 0;
for (let i = 0; i < pos; i++) if (bits[i] === 0) c++;
return c;
}
function mapToHi(bits, pos) {
let c = 0;
for (let i = 0; i < pos; i++) if (bits[i] === 1) c++;
return c;
}
// k-th smallest (1-indexed k) in node.seq[l..r] (0-indexed, inclusive)
// Returns { value, steps } where steps is array of { node, l, r, k, dir }
function kthSmallest(node, l, r, k) {
const steps = [];
function go(nd, l, r, k) {
if (nd.leaf) {
steps.push({ node: nd, l, r, k, dir: 'leaf' });
return nd.value;
}
const zeros = countZeros(nd.bits, l, r);
const dir = k <= zeros ? 'lo' : 'hi';
steps.push({ node: nd, l, r, k, zeros, dir });
if (dir === 'lo') {
const nl = mapToLo(nd.bits, l);
const nr = nl + zeros - 1;
return go(nd.left, nl, nr, k);
} else {
const ones_before_l = mapToHi(nd.bits, l);
const nl = ones_before_l;
const nr = nl + (r - l + 1 - zeros) - 1;
return go(nd.right, nl, nr, k - zeros);
}
}
const value = go(node, l, r, k);
return { value, steps };
}
// Render the original sequence with range highlighted
function renderSeq(l, r) {
const row = document.getElementById('seqRow');
row.innerHTML = '';
SEQ.forEach((v, i) => {
const d = document.createElement('div');
d.className = 'seq-cell' + (i >= l && i <= r ? ' in-range' : '');
d.textContent = v;
row.appendChild(d);
});
}
// Render tree levels with bits highlighted for active range
function renderTree(steps) {
const area = document.getElementById('treeArea');
area.innerHTML = '';
if (!steps || steps.length === 0) return;
steps.forEach((step, si) => {
const nd = step.node;
if (nd.leaf) {
const lbl = document.createElement('div');
lbl.className = 'level-label';
lbl.textContent = fmt('Step %si%%v%: leaf → answer = %value%', { si: si, v: 1, value: nd.value });
area.appendChild(lbl);
return;
}
const lbl = document.createElement('div');
lbl.className = 'level-label';
const info = step.dir === 'lo'
? fmt('zeros in [%l%,%r%] = %zeros% ≥ k=%k% → go LEFT (≤%mid%)', { l: step.l, r: step.r, zeros: step.zeros, k: step.k, mid: nd.mid })
: fmt('zeros in [%l%,%r%] = %zeros% < k=%k% → go RIGHT (>%mid%)', { l: step.l, r: step.r, zeros: step.zeros, k: step.k, mid: nd.mid });
lbl.textContent = fmt('Step %si%%v% [%lo%..%hi%]: %info%', { si: si, v: 1, lo: nd.lo, hi: nd.hi, info: info });
area.appendChild(lbl);
const row = document.createElement('div');
row.className = 'level-row';
nd.bits.forEach((b, i) => {
if (i > 0 && i === step.l) {
const sep = document.createElement('div'); sep.className = 'sep'; row.appendChild(sep);
}
const d = document.createElement('div');
const inRange = i >= step.l && i <= step.r;
d.className = 'bit-cell ' + (b === 0 ? 'lo' : 'hi') + (inRange ? ' active' : '');
d.textContent = b;
row.appendChild(d);
if (i === step.r && step.r < nd.bits.length - 1) {
const sep = document.createElement('div'); sep.className = 'sep'; row.appendChild(sep);
}
});
area.appendChild(row);
});
}
function run() {
const l0 = parseInt(document.getElementById('inL').value, 10) - 1; // 0-indexed
const r0 = parseInt(document.getElementById('inR').value, 10) - 1;
const k = parseInt(document.getElementById('inK').value, 10);
const res = document.getElementById('result');
if (isNaN(l0) || isNaN(r0) || isNaN(k) || l0 < 0 || r0 >= N || l0 > r0 || k < 1 || k > r0 - l0 + 1) {
res.textContent = '⚠ Invalid input. Check that 1 ≤ l ≤ r ≤ 16 and 1 ≤ k ≤ (r−l+1).';
res.className = 'result err';
renderSeq(-1, -1); renderTree(null);
return;
}
renderSeq(l0, r0);
const { value, steps } = kthSmallest(ROOT, l0, r0, k);
renderTree(steps);
res.textContent = fmt('The %k%-th smallest value in positions [%l0%%v%, %r0%%v%] is %value%.', { k: k, l0: l0, v: 1, r0: r0, value: value });
res.className = 'result ok';
console.log('k-th smallest answer:', value, '| steps:', steps.length);
}
function reset() {
document.getElementById('inL').value = 2;
document.getElementById('inR').value = 10;
document.getElementById('inK').value = 3;
document.getElementById('treeArea').innerHTML = '';
document.getElementById('result').textContent = '';
document.getElementById('result').className = 'result';
renderSeq(-1, -1);
}
document.getElementById('btnFind').addEventListener('click', run);
document.getElementById('btnReset').addEventListener('click', reset);
renderSeq(-1, -1);
Comments
Loading comments...