Solution code
const FIRST = 'first', FIRST_COLOR = 'gray';
const LEFT = 'LEFT', LEFT_COLOR = 'orange';
const RIGHT = 'RIGHT', RIGHT_COLOR = 'blue';
function visMainFunction(inputNums) {
initClassProperties();
explanationAndColorPaletteNotifications();
notification(`
🔍 <b>Goal:</b> find all <b>unique triplets</b> <code>(a, b, c)</code> such that <b>a + b + c = 0</b>.<br/>
📌 We first <b>sort</b> the array. Sorting allows us to use a clean <b>two-pointer</b> strategy after fixing one number.
`);
startBatch();
inputNums.sort((a, b) => a - b);
const nums = VisArray.from(inputNums);
const result = new VisArray().options({
labelExtractionPolicy: (data) => data.join(', ')
});
let firstIndex = 0;
makeVisVariable(firstIndex).registerAsIndexPointer(nums, FIRST);
endBatch();
while (firstIndex < nums.length - 2) {
startPartialBatch();
notification(`
📌 Select <b>${makeColoredSpan('nums[firstIndex]', FIRST_COLOR)}</b> =
<b>${makeColoredSpan(nums[firstIndex], FIRST_COLOR)}</b> as the <b>first</b> number in the triplet.
`);
if (firstIndex !== 0 && nums[firstIndex] === nums[firstIndex - 1]) {
notification(`
🔹 <b>Skipping duplicate first value</b> at index <b>${makeColoredSpan(firstIndex, FIRST_COLOR)}</b>
(value <b>${makeColoredSpan(nums[firstIndex], FIRST_COLOR)}</b>).<br/>
We’ve already used this value as the <b>first</b> number and explored all pairs while its duplicates were available as second/third numbers.
Reusing it as the first would only recreate triplets, so we skip to keep results <b>unique</b>.
`);
firstIndex += 1;
continue;
}
const firstNum = nums[firstIndex];
let leftIndex = firstIndex + 1;
let rightIndex = nums.length - 1;
notification(`
🎯 Use two pointers with fixed first value:<br/>
• ${makeColoredSpan('leftIndex', LEFT_COLOR)} = ${makeColoredSpan(leftIndex, LEFT_COLOR)}
(<code>nums[leftIndex]</code> = <b>${makeColoredSpan(nums[leftIndex], LEFT_COLOR)}</b>)<br/>
• ${makeColoredSpan('rightIndex', RIGHT_COLOR)} = ${makeColoredSpan(rightIndex, RIGHT_COLOR)}
(<code>nums[rightIndex]</code> = <b>${makeColoredSpan(nums[rightIndex], RIGHT_COLOR)}</b>)
`);
const leftVis = makeVisVariable(leftIndex).registerAsIndexPointer(nums, LEFT).createVis(false);
const rightVis = makeVisVariable(rightIndex).registerAsIndexPointer(nums, RIGHT).createVis(false);
while (leftIndex < rightIndex) {
const total = nums[leftIndex] + nums[rightIndex] + firstNum;
notification(`
🔎 Check triplet:
(<b>${makeColoredSpan(firstNum, FIRST_COLOR)}</b>,
<b>${makeColoredSpan(nums[leftIndex], LEFT_COLOR)}</b>,
<b>${makeColoredSpan(nums[rightIndex], RIGHT_COLOR)}</b>) → sum = <b>${total}</b>
`);
if (total === 0) {
notification(`
✅ <b>Valid triplet</b> found:
(<b>${makeColoredSpan(firstNum, FIRST_COLOR)}</b>,
<b>${makeColoredSpan(nums[leftIndex], LEFT_COLOR)}</b>,
<b>${makeColoredSpan(nums[rightIndex], RIGHT_COLOR)}</b>).
Adding to <b>result</b>.
`);
result.push([firstNum, nums[leftIndex], nums[rightIndex]]);
notification(`
We've already explored all pairs where the current duplicate values of <b>nums[leftIndex]</b> and <b>nums[rightIndex]</b> were available as second/third numbers.<br/>
Keeping them would only regenerate the <b>same triplet</b> with this fixed first.<br/>
We advance ${makeColoredSpan('leftIndex', LEFT_COLOR)} to the <b>last occurrence</b> of its current value and retreat ${makeColoredSpan('rightIndex', RIGHT_COLOR)} to the <b>first occurrence</b> of its current value before moving both inward to land on <b>new values</b> to form fresh triplets.
`);
while (leftIndex < rightIndex && nums[leftIndex + 1] === nums[leftIndex]) leftIndex += 1;
while (leftIndex < rightIndex && nums[rightIndex - 1] === nums[rightIndex]) rightIndex -= 1;
notification(`
↔️ Moving pointers inward to shrink the window and land on new values of ${makeColoredSpan('leftIndex', LEFT_COLOR)} and ${makeColoredSpan('rightIndex', RIGHT_COLOR)}:
${makeColoredSpan('leftIndex', LEFT_COLOR)} → ${makeColoredSpan(leftIndex+1, LEFT_COLOR)},
${makeColoredSpan('rightIndex', RIGHT_COLOR)} → ${makeColoredSpan(rightIndex-1, RIGHT_COLOR)}.
`);
leftIndex += 1;
rightIndex -= 1;
} else if (total < 0) {
notification(`
➡️ Sum <b>${total}</b> is <b>< 0</b>.
Increase the sum by moving ${makeColoredSpan('leftIndex', LEFT_COLOR)} one step right:
${makeColoredSpan('leftIndex', LEFT_COLOR)} = ${makeColoredSpan(leftIndex, LEFT_COLOR)} → ${makeColoredSpan(leftIndex + 1, LEFT_COLOR)}.
`);
leftIndex += 1;
} else {
notification(`
⬅️ Sum <b>${total}</b> is <b>> 0</b>.
Decrease the sum by moving ${makeColoredSpan('rightIndex', RIGHT_COLOR)} one step left:
${makeColoredSpan('rightIndex', RIGHT_COLOR)} = ${makeColoredSpan(rightIndex, RIGHT_COLOR)} → ${makeColoredSpan(rightIndex - 1, RIGHT_COLOR)}.
`);
rightIndex -= 1;
}
}
leftVis.destroyVis();
rightVis.destroyVis();
notification(`
🔄 Move to next <b>${makeColoredSpan('firstIndex', FIRST_COLOR)}</b>:
${makeColoredSpan(firstIndex, FIRST_COLOR)} → ${makeColoredSpan(firstIndex + 1, FIRST_COLOR)}.
`);
firstIndex += 1;
endBatch();
}
notification(`🏁 <b>Finished!</b> Returning all unique triplets.`);
return result;
}
function explanationAndColorPaletteNotifications() {
explanationNotification();
notification(`
🎨 <b>Color Palette</b><br/>
• <span style="color:gray"><b>firstIndex / nums[firstIndex]</b></span><br/>
• <span style="color:orange"><b>leftIndex / nums[leftIndex]</b></span><br/>
• <span style="color:blue"><b>rightIndex / nums[rightIndex]</b></span>
`);
}
function initClassProperties() {
setClassProperties(LEFT, { backgroundColor: LEFT_COLOR });
setClassProperties(RIGHT, { backgroundColor: RIGHT_COLOR });
setClassProperties(FIRST, { backgroundColor: FIRST_COLOR });
}