DEV Community

vc7
vc7

Posted on

Day 1: Historian Hysteria | Advent of Code 2024 | Swift | 中文

引子

看了 Advent of Code 要開始的推文一直猶豫要不要跳進來;因為往年年末無論是自己上 Qiita 開坑,或是鐵人賽到最後不是失敗就是都很累。

不過因為最後東看西看,最近也有在做 LeetCode POTD ,雖然晚了一天還是決定來做了


題目

題目形式

分成兩個部分

  • Part 1 - 算出星星之間的距離總和
  • Part 2 - 算出相似值

資料整形

藉由觀察,資料源以 \n 分行,並用 三個空格 分隔左右半邊

3   4
4   3
2   5
1   3
3   9
3   3
Enter fullscreen mode Exit fullscreen mode

puzzle 的宣告方式

官方會根據不同的帳號提供不一樣的 puzzle ,這邊我直接複製貼到 Xcode 去執行。請自行替換自己的 puzzle 。

let puzzle = """
3   4
4   3
2   5
1   3
3   9
3   3
"""
Enter fullscreen mode Exit fullscreen mode

程式碼

在這裡就把資料源通過 兩次分割 以及 轉型 整理成兩個整數陣列:

func grouping(with puzzle: String) -> (left: [Int], right: [Int]) {
    puzzle
        // 分行
        .components(separatedBy: "\n")
        .map {
            /// !!!: 為求文章表示簡潔,這裡不做 error handling
            /// 1) 分隔字串 -> 2) 轉型成整數
            $0.components(separatedBy: "   ").compactMap(Int.init)
        }
        .reduce(into: (left: [Int](), right: [Int]())) {
            $0.left.append($1[0])
            $0.right.append($1[1])
        }
}
Enter fullscreen mode Exit fullscreen mode

為了可讀性,也可以另外宣告一個結構來替代 tupple 。


第一部分

前半一大段其實都在說故事,沒有解題的關鍵資訊(笑)

題意

數列分 左數列右數列,題目要求個字排列之後,算出數值之間的總和 - 最小和最小的距離、第二小和第二小的距離,以此類推到最大和最大的距離。

當計算距離的相減後有可能會是 負值,因此在加總前可以使用 abs 方法取絕對值作為距離。

程式碼

func totalDistance(with puzzle: String) -> Int {
    var (left, right) = grouping(with: puzzle)
    left.sort()
    right.sort()

    var sum = 0

    for index in 0..<left.count {
        sum += abs(right[index] - left[index])
    }

    return sum
}
Enter fullscreen mode Exit fullscreen mode

執行

let puzzle = """
  ... 省略
"""

let result = totalDistance(with: puzzle)
Enter fullscreen mode Exit fullscreen mode

result 及為結果

複雜度分析

  • 時間複雜度 O(nlogn)
    • O(nlogn + n) -> O(nlogn)
      • 排序 - O(nlogn)
      • 走訪 - O(n)
  • 空間複雜度 O(n)
    • 有建立 2 個陣列來存放,省略常數項後為 O(n)

第二部分

題意

計算相對於 半邊數列的相似度,算出每一行的 當前值 * 出現次數 後再加總。以官方的範例為例:

3   4
4   3
2   5
1   3
3   9
3   3
Enter fullscreen mode Exit fullscreen mode

左半邊數列

3 => 右數列中出現 3 次 => 3 * 3 = 9
4 => 右數列中出現 1 次 => 4 * 1 = 4
2 => 右數列中出現 0 次 => 2 * 0 = 0
1 => 右數列中出現 0 次 => 1 * 0 = 0
3 => 右數列中出現 3 次 => 3 * 3 = 9
3 => 右數列中出現 3 次 => 3 * 3 = 9
Enter fullscreen mode Exit fullscreen mode

總和:

9 + 4 + 0 + 0 + 9 + 9 = 31
Enter fullscreen mode Exit fullscreen mode

資料結構與想法

首先,先用 hash map 來計數每個數字在右數列中的出現次數。

接著再走訪左數列,再邊用題目提供的算式把總和算出來。

程式碼

func similarity(with puzzle: String) -> Int {
    let (left, right) = grouping(with: puzzle)
    var map = [Int: Int]()

    for number in right {
        map[number, default: 0] += 1
    }

    var sum = 0

    for number in left {
        sum += number * map[number, default: 0]
    }

    return sum
}
Enter fullscreen mode Exit fullscreen mode

或是加總的地方也可以用 reduce

func simularity(puzzle: String) -> Int {
    let (left, right) = grouping(with: puzzle)
    var map = [Int: Int]()

    for number in right {
        map[number, default: 0] += 1
    }

    // 改寫的部分
    return left.reduce(into: 0) {
        $0 += $1 * map[$1, default: 0]
    }
}
Enter fullscreen mode Exit fullscreen mode

執行

let puzzle = """
  ... 省略
"""

let result = similarity(with: puzzle)
Enter fullscreen mode Exit fullscreen mode

result 及為結果

複雜度分析

  • 時間複雜度 O(n)
    • 這個部分都只有線性走訪
  • 空間複雜度 O(n)
    • 左右數列: O(2n) -> O(n)
    • 計數用的 hash map: 最壞情形就是每個數值都不一樣 -> O(n)

結語

以上,希望能 Advent of Code 2024 能做完。

如果有寫錯或有建議請留言讓我知道。如果有找到更好的解法,我會再多發一篇補充。

Top comments (0)