Advent of Code

使用 python 编写, 没有整理代码, 所以非常乱(变量乱取名, 没有注释, 逻辑奇怪, 并非最佳实现)
可以使用 gpt 相关工具辅助查看

如果没有特意说明, 变量 a 统一存放所有原始字符串

第十七题

https://adventofcode.com/2024/day/17

1题

尝试模拟计算机, 寄存器+特定操作码

题非常麻烦, 建议看原文

Details

def main():
    input_data = a.splitlines()
    
    # Parse initial register values
    registers = {
        'A': int(input_data[0].split(': ')[1]),
        'B': int(input_data[1].split(': ')[1]),
        'C': int(input_data[2].split(': ')[1]),
    }
    
    # Parse program
    program = list(map(int, input_data[4].split(': ')[1].split(',')))
    
    output = []
    ip = 0
    program_length = len(program)
    
    def get_combo_value(operand):
        if operand == 4:
            return registers['A']
        elif operand == 5:
            return registers['B']
        elif operand == 6:
            return registers['C']
        else:
            return operand  # 0-3
    
    while ip < program_length:
        if ip + 1 >= program_length:
            break  # Invalid instruction, halt
        opcode = program[ip]
        operand = program[ip + 1]
        
        if opcode == 0:  # adv
            denominator = 2 ** get_combo_value(operand)
            registers['A'] = registers['A'] // denominator
            ip += 2
        elif opcode == 1:  # bxl
            registers['B'] ^= operand
            ip += 2
        elif opcode == 2:  # bst
            registers['B'] = get_combo_value(operand) % 8
            ip += 2
        elif opcode == 3:  # jnz
            if registers['A'] != 0:
                ip = operand
            else:
                ip += 2
        elif opcode == 4:  # bxc
            registers['B'] ^= registers['C']
            ip += 2
        elif opcode == 5:  # out
            output_value = get_combo_value(operand) % 8
            output.append(str(output_value))
            ip += 2
        elif opcode == 6:  # bdv
            denominator = 2 ** get_combo_value(operand)
            registers['B'] = registers['A'] // denominator
            ip += 2
        elif opcode == 7:  # cdv
            denominator = 2 ** get_combo_value(operand)
            registers['C'] = registers['A'] // denominator
            ip += 2
        else:
            ip += 2  # Invalid opcode, skip
        
    print(','.join(output))

if __name__ == "__main__":
    main()

2题

在给定操作码下, 推断最小的寄存器中的满足条件的值(寄存器中的值等于输出的值)

Details

program = list(map(int, a.splitlines()[4].split(': ')[1].split(',')))
n = len(program)
program = program[::-1]

choices = next_choices = [0]
for oo in program:
    choices = next_choices
    next_choices = []
    while choices:
        a = choices.pop()
        for i in range(8):
            aa = (a << 3) | i
            bb = i ^ 1
            cc = aa >> bb
            bb = (bb ^ cc ^ 4) % 8
            if bb == oo:
                next_choices.append(aa)
    # print(next_choices)
    
print(min(next_choices))

# z = (A % 8) ^ 1
# out = z ^ (A >> z) ^ 4

第十八题

https://adventofcode.com/2024/day/18

1题

走二维迷宫

Details

import heapq
from collections import defaultdict

# print(all_map)

start = (0, 0)
end = (70, 70)




flag = False
temp = a.splitlines()
temp = temp[:1024]
# while not flag:
all_map = [['.' for _ in range(71)] for _ in range(71)]
for i in temp:
    x, y = map(int, i.split(','))
    all_map[x][y] = '#'


def get_allow_pos(pos):
    for i in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
        next_step = (pos[0] + i[0], pos[1] + i[1])
        if len(all_map) > next_step[0] >= 0 and len(all_map[0]) > next_step[1] >= 0:
            if all_map[next_step[0]][next_step[1]] == '.':
                yield (pos[0] + i[0], pos[1] + i[1])

node = []
heapq.heappush(node, (0, start))
visited = defaultdict(int)
visited[start] = 0

while node:
    step, (x, y) = heapq.heappop(node)
    if (x, y) == end:
        flag = True
        print(step)
        break
    if visited[(x, y)] < step:
        continue
    for next in get_allow_pos((x, y)):
        if next not in visited or step + 1 < visited[(next[0], next[1])]:
            visited[(next[0], next[1])] = step + 1
            heapq.heappush(node, (step + 1, next))

2题

让迷宫无解的最新的坐标是多少

Details

import heapq
from collections import defaultdict

# print(all_map)

start = (0, 0)
end = (70, 70)

flag = False
temp = a.splitlines()
temp.append('')
while not flag:
    all_map = [['.' for _ in range(71)] for _ in range(71)]
    print(temp.pop())
    for i in temp:
        x, y = map(int, i.split(','))
        all_map[x][y] = '#'

    def get_allow_pos(pos):
        for i in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
            next_step = (pos[0] + i[0], pos[1] + i[1])
            if len(all_map) > next_step[0] >= 0 and len(all_map[0]) > next_step[1] >= 0:
                if all_map[next_step[0]][next_step[1]] == '.':
                    yield (pos[0] + i[0], pos[1] + i[1])

    node = []
    heapq.heappush(node, (0, start))
    visited = defaultdict(int)
    visited[start] = 0

    while node:
        step, (x, y) = heapq.heappop(node)
        if (x, y) == end:
            flag = True
            print(step)
            break
        if visited[(x, y)] < step:
            continue
        for next in get_allow_pos((x, y)):
            if next not in visited or step + 1 < visited[(next[0], next[1])]:
                visited[(next[0], next[1])] = step + 1
                heapq.heappush(node, (step + 1, next))

第十九题

https://adventofcode.com/2024/day/19

1题

给定几个图案片段, 与几种图案(包括不可完成图案), 找出有多少种设计方案

Details

aa = a.splitlines()
has = aa[0]
has = has.split(', ')
all_possible = aa[2:]


def dp(s):
    n = len(s)
    dp_state = [0] * (n + 1)
    dp_state[0] = 1
    for i in range(n):
        if dp_state[i]:
            for j in has:
                l = len(j)
                if i + l <= n and s[i:i+l] == j:
                    dp_state[i+l] = 1
    return dp_state[n]

print(dp('abaa'))

ans = 0
for i in all_possible:
    ans += dp(i)

print(ans)

2题

再将每个设计的不同方法的数量加起来

Details

aa = a.splitlines()
has = aa[0]
has = has.split(', ')
all_possible = aa[2:]

def dp(s):
    n = len(s)
    dp_state = [0] * (n + 1)
    dp_state[0] = 1
    for i in range(n):
        if dp_state[i]:
            for j in has:
                l = len(j)
                if i + l <= n and s[i:i+l] == j:
                    dp_state[i+l] += dp_state[i]
    return dp_state[n]


ans = 0
for i in all_possible:
    ans += dp(i)

print(ans)

第二十题

https://adventofcode.com/2024/day/20

1题

走迷宫, 但允许穿墙 走一步1ps, 允许穿墙一次 最多2 皮秒内禁止碰撞一次
求有多少种能省100ps的穿墙方式

Details

aa = [list(i) for i in a.splitlines()]
walls = [(-1, -1)]
# 开始结束
for i in range(len(aa)):
    for j in range(len(aa[i])):
        if aa[i][j] == 'S':
            start = (i, j)
            aa[i][j] = '.'
        elif aa[i][j] == 'E':
            end = (i, j)
            aa[i][j] = '.'
        elif aa[i][j] == '#':
            walls.append((i, j))

new_walls = []
for i in range(len(walls)):
    if i == 0:
        continue
    if walls[i][0] == 0 or walls[i][0] == len(aa) - 1 or walls[i][1] == 0 or walls[i][1] == len(aa[0]) - 1:
        continue
    # 如果wall的上下或者左右无墙, 则加入new_walls, 注意边界
    if walls[i][0] - 1 >= 0 and aa[walls[i][0] - 1][walls[i][1]] != '#' and walls[i][0] + 1 < len(aa) and aa[walls[i][0] + 1][walls[i][1]] != '#':
        new_walls.append((walls[i][0], walls[i][1]))
    if walls[i][1] - 1 >= 0 and aa[walls[i][0]][walls[i][1] - 1] != '#' and walls[i][1] + 1 < len(aa[0]) and aa[walls[i][0]][walls[i][1] + 1] != '#':
        new_walls.append((walls[i][0], walls[i][1]))

# print(new_walls)
walls = new_walls

print(start, end)

from copy import deepcopy
import heapq
from collections import defaultdict

ans = []
aaa = 0
flag = True
for i in walls:
    all_map = deepcopy(aa)
    if not flag:
        all_map[i[0]][i[1]] = '.'

    def get_allow_pos(pos):
        for i in [(0, 1), (1, 0), (-1, 0), (0, -1)]:
            next_step = (pos[0] + i[0], pos[1] + i[1])
            if len(all_map) > next_step[0] >= 0 and len(all_map[0]) > next_step[1] >= 0:
                if all_map[next_step[0]][next_step[1]] == '.':
                    yield (pos[0] + i[0], pos[1] + i[1])


    node = []
    heapq.heappush(node, (0, start))
    visited = defaultdict(int)
    visited[start] = 0
    while node:
        step, (x, y) = heapq.heappop(node)
        if (x, y) == end:
            if flag:
                aaa = step
                flag = False
            else:
                if aaa - step >= 100:
                    ans.append(aaa - step)
            break
        if visited[(x, y)] < step:
            continue
        for next in get_allow_pos((x, y)):
            if next not in visited or step + 1 < visited[(next[0], next[1])]:
                visited[(next[0], next[1])] = step + 1
                heapq.heappush(node, (step + 1, next))
print(len(ans))

2题

穿墙允许20ps, 作弊不需要使用全部 20 皮秒;作弊可以持续任意时间,最长可达 20 皮秒, 但还是只能用一次, 任何未使用的作弊时间都会丢失;它不能被保存以供以后再次作弊。求节约100ps的方案有多少种

Details

from collections import deque

def read_input():
    grid = [list(i) for i in a.splitlines()]
    return grid

def find_positions(grid, target):
    for r in range(len(grid)):
        for c in range(len(grid[0])):
            if grid[r][c] == target:
                return (r, c)
    return None

def bfs(grid, start):
    rows, cols = len(grid), len(grid[0])
    dist = [[float('inf')] * cols for _ in range(rows)]
    queue = deque()
    start_r, start_c = start
    dist[start_r][start_c] = 0
    queue.append((start_r, start_c))
    while queue:
        r, c = queue.popleft()
        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols:
                if grid[nr][nc] != '#' and dist[nr][nc] == float('inf'):
                    dist[nr][nc] = dist[r][c] + 1
                    queue.append((nr, nc))
    return dist

def main():
    grid = read_input()
    S = find_positions(grid, 'S')
    E = find_positions(grid, 'E')
    if not S or not E:
        print(0)
        return
    dist_S = bfs(grid, S)
    dist_E = bfs(grid, E)
    D = dist_S[E[0]][E[1]]
    if D == float('inf'):
        print(0)
        return
    rows, cols = len(grid), len(grid[0])
    cheat_count = 0
    for a_r in range(rows):
        for a_c in range(cols):
            if dist_S[a_r][a_c] == float('inf'):
                continue
            for b_r in range(rows):
                for b_c in range(cols):
                    if dist_E[b_r][b_c] == float('inf'):
                        continue
                    t_a_b = abs(a_r - b_r) + abs(a_c - b_c)
                    if t_a_b <= 20 and dist_S[a_r][a_c] + dist_E[b_r][b_c] + t_a_b <= D - 100:
                        cheat_count += 1
    print(cheat_count)

if __name__ == "__main__":
    main()

第二十一题

https://adventofcode.com/2024/day/21

1题

操纵 控制方向盘 去 操纵 控制方向盘 去 操纵 控制方向盘 去操纵 数字键盘

Details

aa = a.splitlines()

def is_available(x, y, type=0):
    if type == 0:  # 数字键盘
        if x < 0 or y < 0 or x > 2 or y > 3:
            return False
        
        if x == 0 and y == 3:
            return False
        return True
    elif type == 1:
        if x < 0 or y < 0 or x > 2 or y > 1:  # 移动
            return False
        if x == 0 and y == 0:
            return False
    else:
        raise ValueError("Invalid type")


def get_keyboard():
    return [
        [7, 8, 9],
        [4, 5, 6],
        [1, 2, 3],
        ['*', 0, 'A']
    ]

def get_keyboard_map():
    return {
        '1': (2, 0),
        '2': (2, 1),
        '3': (2, 2),
        '4': (1, 0),
        '5': (1, 1),
        '6': (1, 2),
        '7': (0, 0),
        '8': (0, 1),
        '9': (0, 2),
        '*': (3, 0),
        '0': (3, 1),
        'A': (3, 2)
    }

def get_posboard():
    return [
        ['*', '^', 'A'],
        ['<', 'v', '>'],
    ]

def get_posboard_map():
    return {
        '^': (0, 1),
        'v': (1, 1),
        '<': (1, 0),
        '>': (1, 2),
        'A': (0, 2),
        '*': (0, 0),
    }


def get_path(way: str, type=0):
    ans = ''
    if type == 0:  # 数字键盘
        now_pos = (3, 2)
        keyboard_map = get_keyboard_map()
        for i in range(len(way)):
            new_pos = keyboard_map[way[i]]
            if now_pos[0] == 3 and new_pos[1] == 0:
                for step in range(abs(now_pos[0] - new_pos[0])):
                    ans += '^' if now_pos[0] > new_pos[0] else 'v'
                for step in range(abs(now_pos[1] - new_pos[1])):
                    ans += '<' if now_pos[1] > new_pos[1] else '>'
            else:
                for step in range(abs(now_pos[1] - new_pos[1])):
                    ans += '<' if now_pos[1] > new_pos[1] else '>'
                for step in range(abs(now_pos[0] - new_pos[0])):
                    ans += '^' if now_pos[0] > new_pos[0] else 'v'

            ans += 'A'
            now_pos = new_pos

    if type == 1:  # 方向键盘
        now_pos = (0, 2)
        posboard_map = get_posboard_map()
        for i in range(len(way)):
            new_pos = posboard_map[way[i]]
            if now_pos[0] == 0 and new_pos[1] == 0:
                for step in range(abs(now_pos[0] - new_pos[0])):
                    ans += '^' if now_pos[0] > new_pos[0] else 'v'
                for step in range(abs(now_pos[1] - new_pos[1])):
                    ans += '<' if now_pos[1] > new_pos[1] else '>'
            else:
                for step in range(abs(now_pos[1] - new_pos[1])):
                    ans += '<' if now_pos[1] > new_pos[1] else '>'
                for step in range(abs(now_pos[0] - new_pos[0])):
                    ans += '^' if now_pos[0] > new_pos[0] else 'v'    
            ans += 'A'
            now_pos = new_pos

    return ans

t = 0
for i in aa:
    first = get_path(i)
    second = get_path(first, type=1)
    third = get_path(second, type=1)
    t += len(third) * int(i[:-1])
    print('#########')
    print(first)
    print(second)
    print(third)
    print(len(third), i[:-1])

print(t)

2题

上述一题中的 控制方向盘 变为25个

未解决

第二十二题

https://adventofcode.com/2024/day/22

1题

按规律计算

Details

def mix(num, secret):
    return num ^ secret

def prune(num):
    return num % 16777216

def cal(num):
    secret = num * 64
    num = prune(mix(num, secret))
    secret = num // 32
    num = prune(mix(num, secret))
    secret = num * 2048
    num = prune(mix(num, secret))
    return num


aa = [int(i) for i in a.split('\n')]
ans = 0
for i in aa:
    t = i
    for j in range(2000):
        t = cal(t)
    ans += t
print(ans)

2题

更新计算方式, 根据序列判断方式来计算最大收益

Details

def mix(num, secret):
    return num ^ secret

def prune(num):
    return num % 16777216

def cal(num):
    secret = num * 64
    num = prune(mix(num, secret))
    secret = num // 32
    num = prune(mix(num, secret))
    secret = num * 2048
    num = prune(mix(num, secret))
    return num

def generate_secrets(initial_secret, num=2000):
    secrets = [initial_secret]
    for _ in range(num):
        secret = cal(secrets[-1])
        secrets.append(secret)
    return secrets

def generate_prices_and_changes(secrets):
    prices = [s % 10 for s in secrets]
    changes = [prices[i] - prices[i-1] for i in range(1, len(prices))]
    return prices, changes

def change_seq_to_index(seq):
    a, b, c, d = seq
    a = a + 9
    b = b + 9
    c = c + 9
    d = d + 9
    index = a * 19**3 + b * 19**2 + c * 19 + d
    return index

def main():
    buyers = [int(i) for i in a.splitlines()]
    total_bananas = [0] * (19**4)
    
    for buyer_initial in buyers:
        secrets = generate_secrets(buyer_initial)
        prices, changes = generate_prices_and_changes(secrets)
        found = {}
        for j in range(len(changes)-3):
            seq = (changes[j], changes[j+1], changes[j+2], changes[j+3])
            if seq not in found:
                if j+4 < len(prices):
                    found[seq] = prices[j+4]
                else:
                    found[seq] = 0  # 防止越界
        for seq, price in found.items():
            index = change_seq_to_index(seq)
            total_bananas[index] += price
    max_bananas = max(total_bananas)
    print("最大香蕉数:", max_bananas)

if __name__ == "__main__":
    main()

第二十三题

https://adventofcode.com/2024/day/23

1题

图的创建, 找到三台互联 至少有一台以t开头的节点

Details

from itertools import combinations

# 读取输入数据
lines = a.splitlines()

# 构建邻接表
connections = {}
for line in lines:
    a, b = line.strip().split('-')
    if a not in connections:
        connections[a] = set()
    if b not in connections:
        connections[b] = set()
    connections[a].add(b)
    connections[b].add(a)

# 获取所有电脑名称
nodes = list(connections.keys())

# 生成所有三个电脑的组合
triplets = combinations(nodes, 3)

# 检查是否完全连接
def is_complete(triplet):
    a, b, c = triplet
    return b in connections[a] and c in connections[a] and c in connections[b]

# 统计符合条件的三元组数量
count = 0
for triplet in triplets:
    if is_complete(triplet):
        if any(name.startswith('t') for name in triplet):
            count += 1

print(f"符合条件的三元组共有 {count} 个。")

2题

最大的一组相互连接的计算机, 按字母排序输出

Details

# 读取输入数据
lines = a.splitlines()

# 构建邻接表
connections = {}
for line in lines:
    a, b = line.strip().split('-')
    if a not in connections:
        connections[a] = set()
    if b not in connections:
        connections[b] = set()
    connections[a].add(b)
    connections[b].add(a)

# Bron-Kerbosch算法找所有团
def bron_kerbosch(R, P, X):
    if not P and not X:
        cliques.append(R)
        return
    u = next(iter(P.union(X))) if P.union(X) else None
    if u:
        for v in P - connections[u]:
            bron_kerbosch(R + [v], P.intersection(connections[v]), X.intersection(connections[v]))
            P.remove(v)
            X.add(v)
    else:
        cliques.append(R)

cliques = []
bron_kerbosch([], set(connections.keys()), set())

# 找到最大的团
max_clique = max(cliques, key=lambda x: len(x))

# 生成密码
password = ','.join(sorted(max_clique))

print(password)

第二十四题

https://adventofcode.com/2024/day/25

1题

模拟逻辑门输入输出

Details

from collections import deque


def main():
    input_data = a
    parts = input_data.split('\n\n')
    if len(parts) < 2:
        initial_values_section = parts[0]
        gate_definitions_section = ''
    else:
        initial_values_section, gate_definitions_section = parts

    # 解析初始值
    wire_values = {}
    for line in initial_values_section.splitlines():
        if not line.strip():
            continue
        wire, value = line.split(': ')
        wire_values[wire] = int(value)

    # 解析逻辑门定义
    gate_definitions = {}
    for line in gate_definitions_section.splitlines():
        if not line.strip():
            continue
        left, right = line.split(' -> ')
        for op in [' AND ', ' OR ', ' XOR ']:
            if op in left:
                inputs = left.split(op)
                operator = op.strip()
                break
        else:
            continue  # 忽略不支持的操作符
        gate_definitions[right] = (operator, inputs[0], inputs[1])


    # 找到所有输入都有值的逻辑门,加入队列
    queue = deque()
    for output_wire, (op, in1, in2) in gate_definitions.items():
        if in1 in wire_values and in2 in wire_values:
            queue.append(output_wire)

    already_computed = set()
    # 处理队列中的逻辑门
    while queue:
        output_wire = queue.popleft()
        op, in1, in2 = gate_definitions[output_wire]
        value1 = wire_values[in1]
        value2 = wire_values[in2]
        if op == 'AND':
            result = value1 & value2
        elif op == 'OR':
            result = value1 | value2
        elif op == 'XOR':
            result = value1 ^ value2
        else:
            continue  # 无效的操作符
        wire_values[output_wire] = result
        already_computed.add(output_wire)
        # 检查是否有新的逻辑门可以计算
        for gw, (gate_op, gw_in1, gw_in2) in gate_definitions.items():
            if gw not in wire_values and gw_in1 in wire_values and gw_in2 in wire_values:
                if gw not in already_computed and gw not in queue:
                    queue.append(gw)

    # 收集所有以z开头的电线的值
    z_wires = [wire for wire in wire_values if wire.startswith('z')]
    # 按编号排序,z00, z01, ..., z12
    z_wires_sorted = sorted(z_wires, key=lambda x: int(x[1:]))
    # 组合成二进制字符串,从z12到z00
    binary_str = ''.join(str(wire_values[w]) for w in reversed(z_wires_sorted))
    # 转换成十进制
    decimal_output = int(binary_str, 2)
    print(decimal_output)

if __name__ == "__main__":
    main()

2题

未完成

第二十五题

https://adventofcode.com/2024/day/25

未完成

转载无需注明出处