โจทย์
กำหนด array arr ของจำนวนเต็มบวก สร้าง Binary Tree ที่:
- โหนดแต่ละตัวมีลูก 0 หรือ 2 ตัว (Full Binary Tree)
- Leaf nodes (inorder) =
arrตามลำดับ - Non-leaf node value = ผลคูณของ max leaf ใน left subtree × max leaf ใน right subtree
หา Minimum Sum ของ non-leaf nodes ทั้งหมด
ตัวอย่าง: arr = [6,2,4] → 2 ต้นไม้เป็นไปได้:
Tree 1: Tree 2:
24 32
/ \ / \
6 12 8 4
/ \ / \
2 4 6 2
Sum = 12+24=36 Sum = 8+24=32 ← Minimum
Output: 32
วิธีแก้ 1: DP (Interval DP) - O(n³)
dp[i][j] = min cost สำหรับ subarray arr[i:j+1]
class Solution:
def mctFromLeafValues(self, arr: list[int]) -> int:
n = len(arr)
memo = {}
def dp(i, j):
if i >= j:
return 0
if (i, j) in memo:
return memo[(i, j)]
res = float('inf')
for k in range(i, j):
# Split at k: left = i..k, right = k+1..j
left_max = max(arr[i:k+1])
right_max = max(arr[k+1:j+1])
cost = left_max * right_max
total = dp(i, k) + dp(k+1, j) + cost
res = min(res, total)
memo[(i, j)] = res
return res
return dp(0, n-1)
# ทดสอบ
ob = Solution()
print(ob.mctFromLeafValues([6,2,4])) # 32
วิธีแก้ 2: Monotonic Stack (Greedy) - O(n) ⭐ Optimal
Key Insight: Non-leaf node = product of 2 adjacent "peaks" ในการ merge เราต้องการ merge ค่าน้อยกับค่ามากที่สุดเท่าที่จะทำได้ (เพื่อลด product)
ใช้ Monotonic Decreasing Stack:
- Push elements เข้า stack
- เมื่อ element ใหม่ > stack top: pop top, cost += popped × min(new_top, current)
- ท้ายสุด: pop ทั้ง stack คูณกับ next element
class Solution:
def mctFromLeafValues(self, arr: list[int]) -> int:
stack = [float('inf')] # Sentinel
total_cost = 0
for num in arr:
# Pop while current >= top (merge small with smaller neighbor)
while stack[-1] <= num:
mid = stack.pop()
total_cost += mid * min(stack[-1], num)
stack.append(num)
# Clean up remaining stack
while len(stack) > 2:
total_cost += stack.pop() * stack[-1]
return total_cost
# ทดสอบ
ob = Solution()
print(ob.mctFromLeafValues([6,2,4])) # 32
print(ob.mctFromLeafValues([4,11,5,10])) # 220
print(ob.mctFromLeafValues([1,2,3,4])) # 20
Output
32 220 20
การทำงานของ Stack (ตัวอย่าง [6,2,4])
stack=[inf]
num=6: stack=[inf, 6]
num=2: stack=[inf, 6, 2]
num=4: 4 > 2 → pop 2, cost += 2 * min(6,4) = 8, stack=[inf,6]
4 < 6 → push, stack=[inf,6,4]
End: pop 4, cost += 4*6 = 24 → total=32
pop 6, cost += 6*inf (skip sentinel)
Complexity
- DP: O(n³) time, O(n²) space
- Monotonic Stack: O(n) time, O(n) space ⭐
สรุป
Minimum Cost Tree From Leaf Values แก้ได้ด้วย Monotonic Stack แบบ Greedy O(n) จุดสำคัญคือ merge ค่าน้อยกับ neighbor ที่น้อยกว่าเสมอ (local optimal = global optimal)
หากมีข้อสงสัย สามารถถามได้ในส่วนความคิดเห็นครับ