Medium草稿★★★★★
TreeDFSDesignHashMap
Patterns🏗️ 資料結構設計🌳 樹形 DFS#️⃣ 雜湊
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

1993Operations On TreeTreesMediumTrees

設計一個支援鎖定、解鎖和升級操作的樹結構。Lock 鎖定節點給指定使用者;Unlock 解鎖節點(需同一使用者);Upgrade 鎖定節點並解鎖所有子孫節點(需節點未鎖定、至少一個子孫被鎖定、所有祖先未鎖定)。

Example:

Input: [“LockingTree”,“lock”,“unlock”,“unlock”,“lock”,“upgrade”,“lock”] > [[[-1,0,0,1,1,2,2]],[2,2],[2,3],[2,2],[4,5],[0,1],[0,1]] Output: [null,true,false,true,true,true,false]

Intuition

TIP

模擬題,關鍵在 upgrade 操作需要檢查祖先鏈和子孫樹。

Approaches

1. Direct Simulation — Lock/Unlock O(1), Upgrade O(n) / O(n)
  • Idea: 用陣列記錄鎖定狀態,Lock/Unlock 直接操作,Upgrade 逐一檢查條件
  • Time: Lock/Unlock O(1),Upgrade O(n)
  • Space: O(n)
class LockingTree(parent: IntArray) {
    private val par = parent
    private val children = Array(parent.size) { mutableListOf<Int>() }
    private val lockedBy = IntArray(parent.size) { -1 } // -1 表示未鎖定

    init {
        for (i in 1 until parent.size) {
            children[parent[i]].add(i)
        }
    }

    fun lock(num: Int, user: Int): Boolean {
        if (lockedBy[num] != -1) return false
        lockedBy[num] = user
        return true
    }

    fun unlock(num: Int, user: Int): Boolean {
        if (lockedBy[num] != user) return false
        lockedBy[num] = -1
        return true
    }

    fun upgrade(num: Int, user: Int): Boolean {
        // 條件 1: 自身未鎖定
        if (lockedBy[num] != -1) return false
        // 條件 2: 所有祖先未鎖定
        var ancestor = par[num]
        while (ancestor != -1) {
            if (lockedBy[ancestor] != -1) return false
            ancestor = par[ancestor]
        }
        // 條件 3: 至少一個子孫被鎖定,並解鎖所有子孫
        if (!unlockDescendants(num)) return false
        // 鎖定當前節點
        lockedBy[num] = user
        return true
    }

    // 解鎖所有子孫,回傳是否至少有一個被鎖定的子孫
    private fun unlockDescendants(node: Int): Boolean {
        var found = false
        for (child in children[node]) {
            if (lockedBy[child] != -1) {
                lockedBy[child] = -1
                found = true
            }
            if (unlockDescendants(child)) found = true
        }
        return found
    }
}
⭐ 2. Optimized Ancestor Check (same idea, cleaner) — Lock/Unlock O(1), Upgrade O(n) / O(n)
  • Idea: 與方法 1 相同,但將三個條件的檢查抽成獨立函數,提高可讀性
  • Time: Lock/Unlock O(1),Upgrade O(n)
  • Space: O(n)
class LockingTree(parent: IntArray) {
    private val par = parent
    private val children = Array(parent.size) { mutableListOf<Int>() }
    private val lockedBy = IntArray(parent.size) { -1 }

    init {
        for (i in 1 until parent.size) {
            children[parent[i]].add(i)
        }
    }

    fun lock(num: Int, user: Int): Boolean {
        if (lockedBy[num] != -1) return false
        lockedBy[num] = user
        return true
    }

    fun unlock(num: Int, user: Int): Boolean {
        if (lockedBy[num] != user) return false
        lockedBy[num] = -1
        return true
    }

    fun upgrade(num: Int, user: Int): Boolean {
        if (lockedBy[num] != -1) return false
        if (hasLockedAncestor(num)) return false
        if (!hasLockedDescendant(num)) return false
        unlockAllDescendants(num)
        lockedBy[num] = user
        return true
    }

    private fun hasLockedAncestor(node: Int): Boolean {
        var curr = par[node]
        while (curr != -1) {
            if (lockedBy[curr] != -1) return true
            curr = par[curr]
        }
        return false
    }

    private fun hasLockedDescendant(node: Int): Boolean {
        for (child in children[node]) {
            if (lockedBy[child] != -1) return true
            if (hasLockedDescendant(child)) return true
        }
        return false
    }

    private fun unlockAllDescendants(node: Int) {
        for (child in children[node]) {
            lockedBy[child] = -1
            unlockAllDescendants(child)
        }
    }
}

🔑 Takeaways