May's Blog

I spent Day 3 hanging out in the lobby with a pile of battery banks and a stubborn escalator. Here is how I cracked both puzzles using a pair of lean JavaScript scripts (part1.js and part2.js).

Part 1 – Two batteries, one number

Each bank is a string of digits. I needed the largest possible two-digit value that preserves order. Rather than evaluate every pair, I sweep the string once while tracking the best “first” digit seen so far. Every new digit forms a candidate pair with that best digit; if the candidate beats the running maximum, I store it, and if the new digit is even better as a first digit, I promote it. This greedy pass extracts the answer for a bank in linear time.

Complexity: For n banks whose lengths sum to L, the runtime is O(L) and the extra memory stays at O(1).

 1const fs = require('fs');
 2const path = require('path');
 3
 4const inputPath = process.argv[2] ?? path.join(__dirname, 'input.txt');
 5
 6function readBanks(filePath) {
 7    try {
 8        return fs
 9        .readFileSync(filePath, 'utf8')
10        .trim()
11        .split(/\r?\n/)
12        .map((line) => line.trim())
13        .filter((line) => line.length > 0);
14    } catch (error) {
15        throw new Error(`Unable to read input file: ${filePath}`);
16    }
17}
18
19function maxTwoDigitValue(bank) {
20    if (bank.length < 2) {
21        return 0;
22    }
23
24    let bestPair = -1;
25    let bestFirst = Number(bank[0]);
26
27    for (let i = 1; i < bank.length; i += 1) {
28        const digit = Number(bank[i]);
29        const candidate = bestFirst * 10 + digit;
30        if (candidate > bestPair) {
31        bestPair = candidate;
32        }
33        if (digit > bestFirst) {
34        bestFirst = digit;
35        }
36    }
37
38    return bestPair;
39}
40
41function solve(banks) {
42    return banks.reduce((sum, bank) => sum + maxTwoDigitValue(bank), 0);
43}
44
45function main() {
46    if (!fs.existsSync(inputPath)) {
47        console.error(`Input file not found: ${inputPath}`);
48        process.exitCode = 1;
49        return;
50    }
51
52    const banks = readBanks(inputPath);
53    if (banks.length === 0) {
54        console.log('0');
55        return;
56    }
57
58    const result = solve(banks);
59    console.log(result.toString());
60}
61
62main();

Part 2 – Twelve digits to rule the escalator

The safety override demanded far more juice: exactly twelve digits per bank. This morphs into the classic “remove k digits to maximize the remaining sequence” problem. I used a monotonic stack:

  1. k = bank.length - 12 is how many digits must be discarded.
  2. While the stack’s last digit is smaller than the current digit and I still have removals left, pop it.
  3. Push the current digit.
  4. After processing the whole bank, trim any extra digits from the end until only twelve remain.

Converting the resulting 12-digit string to BigInt kept the totals precise, and summing across all banks produced the final answer.

Complexity: The same total input length L drives this part. Each digit is pushed and popped at most once, so the runtime is O(L) with O(12) extra storage per bank (effectively O(1)).

 1const fs = require('fs');
 2const path = require('path');
 3
 4const DIGITS_TO_SELECT = 12;
 5const inputPath = process.argv[2] ?? path.join(__dirname, 'input.txt');
 6
 7function readBanks(filePath) {
 8    try {
 9        return fs
10        .readFileSync(filePath, 'utf8')
11        .trim()
12        .split(/\r?\n/)
13        .map((line) => line.trim())
14        .filter((line) => line.length > 0);
15    } catch (error) {
16        throw new Error(`Unable to read input file: ${filePath}`);
17    }
18}
19
20function bestTwelveDigits(bank) {
21    if (bank.length <= DIGITS_TO_SELECT) {
22        return BigInt(bank);
23    }
24
25    const stack = [];
26    let remaining = bank.length - DIGITS_TO_SELECT;
27
28    for (const char of bank) {
29        const digit = char;
30        while (stack.length && remaining > 0 && stack[stack.length - 1] < digit) {
31        stack.pop();
32        remaining -= 1;
33        }
34        stack.push(digit);
35    }
36
37    while (stack.length > DIGITS_TO_SELECT) {
38        stack.pop();
39    }
40
41    return BigInt(stack.join(''));
42}
43
44function solve(banks) {
45    return banks.reduce((sum, bank) => sum + bestTwelveDigits(bank), 0n);
46}
47
48function main() {
49    if (!fs.existsSync(inputPath)) {
50        console.error(`Input file not found: ${inputPath}`);
51        process.exitCode = 1;
52        return;
53    }
54
55    const banks = readBanks(inputPath);
56    if (banks.length === 0) {
57        console.log('0');
58        return;
59    }
60
61    const result = solve(banks);
62    console.log(result.toString());
63}
64
65main();

Lessons learned

With both parts done, the escalator has all the joltage it needs—and I get to keep moving deeper into the North Pole base.

#AdventOfCode #Javascript #AdventOfCode2025