Description
有
n個橘子,每天可以選擇三種操作之一:吃掉一個橘子;若n能被 2 整除,吃掉n/2個;若n能被 3 整除,吃掉2n/3個。求吃完所有橘子的最少天數。
Example:
Input: n = 10 Output: 4
Intuition#
不能用普通 DP(n 太大),但除以 2 或 3 會快速縮小問題規模,用記憶化搜尋 + 貪心跳到最近可整除的數。
- n 最大到 2 * 10^9,普通 DP 不可行
- 關鍵觀察:應該盡量使用除 2 或除 3 操作,減 1 只是為了調整到可整除
- 從 n 開始,先減到能被 2 或 3 整除,然後除以 2 或 3
Approaches#
⭐1: 記憶化搜尋#
- 概念: 對每個 n,先花
n % 2天減到能被 2 整除或花n % 3天減到能被 3 整除,然後遞迴求解n/2或n/3 - 時間複雜度:
O(log^2(n)) - 空間複雜度:
O(log^2(n))
class Solution {
private val memo = HashMap<Int, Int>()
fun minDays(n: Int): Int {
if (n <= 1) return n
memo[n]?.let { return it }
val result = 1 + minOf(
n % 2 + minDays(n / 2),
n % 3 + minDays(n / 3)
)
memo[n] = result
return result
}
}2: BFS#
- 概念: 將問題視為圖的 BFS,但只對除 2 和除 3 的分支做展開,減 1 操作用直接計算代替
- 時間複雜度:
O(log^2(n)) - 空間複雜度:
O(log^2(n))
class Solution {
fun minDays(n: Int): Int {
if (n <= 1) return n
val visited = HashMap<Int, Int>()
val queue = java.util.PriorityQueue<IntArray>(compareBy { it[1] })
queue.add(intArrayOf(n, 0))
visited[n] = 0
while (queue.isNotEmpty()) {
val (cur, days) = queue.poll()
if (cur == 0) return days
if (visited.containsKey(cur) && visited[cur]!! < days) continue
// 減到能被 2 整除然後除 2
val d2 = days + cur % 2 + 1
val n2 = cur / 2
if (!visited.containsKey(n2) || visited[n2]!! > d2) {
visited[n2] = d2
queue.add(intArrayOf(n2, d2))
}
// 減到能被 3 整除然後除 3
val d3 = days + cur % 3 + 1
val n3 = cur / 3
if (!visited.containsKey(n3) || visited[n3]!! > d3) {
visited[n3] = d3
queue.add(intArrayOf(n3, d3))
}
}
return -1
}
}🔑 Takeaways#
- Pattern: 記憶化搜尋 + 貪心跳躍 – 大數值問題不能逐步 DP,要利用乘除快速縮小規模
- 關鍵技巧:
n % 2 + minDays(n/2)表示先花n%2天減 1 調整到偶數,再除以 2;遞迴只會產生 O(log^2 n) 個不同的子問題