aoc/year2020/
day01.rs

1//! # Report Repair
2//!
3//! The straightforward approach is to compare every possible pair of elements for part one and
4//! every possible triple for part two. This would have `O(n²)` and `O(n³)`time complexity respectively.
5//!
6//! We can do better with `O(n)` complexity for part one and `O(n²)` for part two.
7//!
8//! For part one we use an implicit hash table in an array, since values are constrained to between
9//! 0 and 2020 and each value is already perfectly hashed. For each entry we check the index
10//! at its value. If this is marked then we have seen the reciprocal `2020 - value` before
11//! so we can return the product. If not then we mark the reciprocal value in the array.
12//!
13//! Part 2 reuses the pair finding logic, finding the third element by stepping through the slice
14//! one by one and adjusting the target total. To reuse the array without reallocating
15//! (which is slow) we use a `round` value instead of `bool`.
16use crate::util::parse::*;
17
18pub fn parse(input: &str) -> Vec<usize> {
19    input.lines().map(|line| line.unsigned()).collect()
20}
21
22pub fn part1(input: &[usize]) -> usize {
23    let mut hash = [0; 2020];
24    two_sum(input, 2020, &mut hash, 1).unwrap()
25}
26
27pub fn part2(input: &[usize]) -> usize {
28    let mut hash = [0; 2020];
29
30    for i in 0..(input.len() - 2) {
31        let first = input[i];
32        let round = i + 1;
33        let slice = &input[round..];
34        let target = 2020 - first;
35
36        if let Some(product) = two_sum(slice, target, &mut hash, round) {
37            return first * product;
38        }
39    }
40    unreachable!()
41}
42
43fn two_sum(slice: &[usize], target: usize, hash: &mut [usize], round: usize) -> Option<usize> {
44    for &i in slice {
45        if i < target {
46            if hash[i] == round {
47                return Some(i * (target - i));
48            }
49            hash[target - i] = round;
50        }
51    }
52
53    None
54}