Description

設計一個瀏覽器歷史紀錄系統。BrowserHistory(homepage) 初始化,visit(url) 訪問新頁面(清除前進歷史),back(steps) 回退最多 steps 步,forward(steps) 前進最多 steps 步。

Example:

Input: [“BrowserHistory”,“visit”,“visit”,“visit”,“back”,“back”,“forward”,“visit”,“forward”,“back”,“back”], [[“leetcode.com”],[“google.com”],[“facebook.com”],[“youtube.com”],[1],[1],[1],[“linkedin.com”],[2],[2],[7]] Output: [null,null,null,null,“facebook.com”,“google.com”,“facebook.com”,null,“linkedin.com”,“google.com”,“leetcode.com”]

Intuition#

核心思路:使用雙向鏈結串列模擬瀏覽器的前進/後退功能,visit 時截斷前進歷史。

  • 瀏覽器歷史本質是一個可以前後移動的序列
  • visit 新頁面時,當前位置之後的所有歷史都要清除
  • 可以用 ArrayList(簡單)或雙向鏈結串列(更貼近實際)

Approaches#

1: ArrayList#

  • 概念: 用 ArrayList 儲存歷史,cursor 指向當前位置,visit 時截斷 cursor 之後的部分
  • 時間複雜度: visit O(1) 均攤, back/forward O(1)
  • 空間複雜度: O(n) - n 為訪問過的頁面數
class BrowserHistory(homepage: String) {

    private val history = ArrayList<String>()
    private var cursor = 0

    init {
        history.add(homepage)
    }

    fun visit(url: String) {
        // 清除 cursor 之後的歷史
        while (history.size > cursor + 1) {
            history.removeAt(history.size - 1)
        }
        history.add(url)
        cursor++
    }

    fun back(steps: Int): String {
        cursor = maxOf(0, cursor - steps)
        return history[cursor]
    }

    fun forward(steps: Int): String {
        cursor = minOf(history.size - 1, cursor + steps)
        return history[cursor]
    }
}

⭐2: Doubly Linked List#

  • 概念: 每個頁面是一個節點,curr 指向當前頁面,visit 時將新節點接在 curr 後面並截斷後續
  • 時間複雜度: visit O(1), back/forward O(steps)
  • 空間複雜度: O(n)
class BrowserHistory(homepage: String) {

    private class Node(val url: String) {
        var prev: Node? = null
        var next: Node? = null
    }

    private var curr = Node(homepage)

    fun visit(url: String) {
        val node = Node(url)
        curr.next = node
        node.prev = curr
        curr = node
    }

    fun back(steps: Int): String {
        var remaining = steps
        while (remaining > 0 && curr.prev != null) {
            curr = curr.prev!!
            remaining--
        }
        return curr.url
    }

    fun forward(steps: Int): String {
        var remaining = steps
        while (remaining > 0 && curr.next != null) {
            curr = curr.next!!
            remaining--
        }
        return curr.url
    }
}

🔑 Takeaways#

  • Pattern: 瀏覽器歷史是雙向鏈結串列的經典應用場景,前進/後退對應鏈結串列的雙向遍歷
  • 關鍵技巧: visit 時截斷前進歷史,在鏈結串列中只需將 curr.next 指向新節點即可(舊的前進歷史會被 GC 回收)