Solution code
const CURRENT_NUM = 'current_num', CURRENT_NUM_COLOR = 'gold';
const CURRENT_TARGET = 'current_target', CURRENT_TARGET_COLOR = 'blue';
const CONTRIBUTING = 'contributing_dp', CONTRIBUTING_COLOR = 'orange';
function visMainFunction(inputNums, target) {
initClassProperties();
colorSchemaAndExplanationNotifications();
startBatch();
inputNums.sort((a, b) => a - b);
const nums = VisArray.from(inputNums);
const dp = new VisArray(target + 1).fill(0);
dp[0] = 1;
endBatch();
let currentTarget = 1;
makeVisVariable(currentTarget).registerAsIndexPointer(dp, CURRENT_TARGET);
notification(`
• Initialized <code>dp[i]</code> to represent the number of ways to form sum <code>i</code>, all starting at <b>0</b>.<br/>
• Base case: <code>dp[0] = 1</code> — there is exactly one way to form sum 0 (by choosing no numbers).<br/>
• We have <b>already sorted</b> the numbers to enable early termination when a number exceeds the current target.<br/><br/>
▶️ We’ll now build the <code>dp</code> table from <code>${makeColoredSpan('currentTarget', CURRENT_TARGET_COLOR)}</code> = <b>1</b> up to <b>${target}</b>.
`);
while (currentTarget <= target) {
notification(`
🧩 <b>Key idea for target ${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b>
<ul>
<li>We iterate over each number <code>${makeColoredSpan('num', CURRENT_NUM_COLOR)}</code> in <code>nums</code>.</li>
<li>If <code>${makeColoredSpan('num', CURRENT_NUM_COLOR)} > ${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</code>, we stop — since the list is sorted, all following numbers are larger than ${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)} and we cannot reach ${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)} using them.</li>
<li>If <code>${makeColoredSpan('num', CURRENT_NUM_COLOR)} ≤ ${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</code>:
<ul>
<li>We can form new combinations for <b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b> by appending <code>${makeColoredSpan('num', CURRENT_NUM_COLOR)}</code> to every combination that sums to <code>${makeColoredSpan(`${currentTarget} - num`, CONTRIBUTING_COLOR)}</code>.</li>
<li>The number of such combinations is stored in <code>dp[${makeColoredSpan(`${currentTarget} - num`, CONTRIBUTING_COLOR)}]</code>, which we’ve already computed because we process targets in increasing order.</li>
<li>Hence, we <b>unlock</b> <code>dp[${makeColoredSpan(`${currentTarget} - num`, CONTRIBUTING_COLOR)}]</code> new ways to reach <b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b>.</li>
<li>We update: <code>dp[${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}] += dp[${makeColoredSpan(`${currentTarget} - num`, CONTRIBUTING_COLOR)}]</code>.</li>
</ul>
</li>
</ul>
`);
let numIndex = 0;
makeVisVariable(numIndex).registerAsIndexPointer(nums, CURRENT_NUM);
while (numIndex < nums.length) {
startPartialBatch();
const num = nums[numIndex];
if (num > currentTarget) {
notification(`⏩ <b>Early termination</b>: ${makeColoredSpan('num', CURRENT_NUM_COLOR)} (<b>${makeColoredSpan(num, CURRENT_NUM_COLOR)}</b>) exceeds current target (<b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b>). No need to check further.`);
endBatch();
break;
}
const sourceIndex = currentTarget - num;
dp.makeVisManagerForIndex(sourceIndex).addClass(CONTRIBUTING);
notification(`
🧩 <b>Considering number ${makeColoredSpan(num, CURRENT_NUM_COLOR)}</b> for target <b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b>:
<ul>
<li>To form <b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b>, we can append <code>${makeColoredSpan(num, CURRENT_NUM_COLOR)}</code> to any combination that already sums to <code>${makeColoredSpan(sourceIndex, CONTRIBUTING_COLOR)}</code>.</li>
<li>Therefore, we look at <code>dp[${makeColoredSpan(sourceIndex, CONTRIBUTING_COLOR)}]</code> — the number of ways to reach <b>${makeColoredSpan(sourceIndex, CONTRIBUTING_COLOR)}</b>.</li>
<li>Each of those combinations becomes a new valid way to reach <b>${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}</b> when we add <b>${makeColoredSpan(num, CURRENT_NUM_COLOR)}</b> to the combination.</li>
<li>Thus, we increase <code>dp[${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}]</code> by <code>dp[${makeColoredSpan(sourceIndex, CONTRIBUTING_COLOR)}]</code>.</li>
<li>Update:
<code>dp[${makeColoredSpan(currentTarget, CURRENT_TARGET_COLOR)}] = ${dp[currentTarget]} (existing) + ${dp[sourceIndex]} (new ways) → ${dp[currentTarget] + dp[sourceIndex]}</code>.
</li>
</ul>
`);
dp[currentTarget] += dp[sourceIndex];
dp.makeVisManagerForIndex(sourceIndex).removeClass(CONTRIBUTING);
numIndex += 1;
endBatch();
}
startBatch();
numIndex = -1;
currentTarget += 1;
endBatch();
}
notification(`🏁 <b>Finished!</b> Number of ways to reach the target <b>${target}</b> is <b>${dp[target]}</b>.`);
return dp[target];
}
function colorSchemaAndExplanationNotifications() {
explanationNotification();
notification(`
🎨 <b>Visualization Color Legend</b><br/><br/>
<ul>
<li>
<span style="background-color: ${CURRENT_TARGET_COLOR}; padding: 0 6px; border-radius: 4px;"> </span>
<b>Current target</b> (<code>${makeColoredSpan('currentTarget', CURRENT_TARGET_COLOR)}</code>):
The index in the <code>dp</code> table currently being updated.
</li>
<li>
<span style="background-color: ${CURRENT_NUM_COLOR}; padding: 0 6px; border-radius: 4px;"> </span>
<b>Current number</b> (<code>${makeColoredSpan('num', CURRENT_NUM_COLOR)}</code>):
The number from <code>nums</code> currently being evaluated to contribute to
<code>dp[${makeColoredSpan('currentTarget', CURRENT_TARGET_COLOR)}]</code>.
</li>
<li>
<span style="background-color: ${CONTRIBUTING_COLOR}; padding: 0 6px; border-radius: 4px;"> </span>
<b>Contributing subtarget</b> (<code>dp[${makeColoredSpan('currentTarget - num', CONTRIBUTING_COLOR)}]</code>):
The previously computed entry that contributes new ways to reach the current target.
</li>
</ul>
`);
}
function initClassProperties() {
setClassProperties(CURRENT_NUM, { backgroundColor: CURRENT_NUM_COLOR });
setClassProperties(CURRENT_TARGET, { backgroundColor: CURRENT_TARGET_COLOR });
setClassProperties(CONTRIBUTING, { backgroundColor: CONTRIBUTING_COLOR });
}