Description
給定整數陣列
nums和整數x,每次操作可以從陣列最左邊或最右邊移除一個元素並從 x 中減去該值。回傳使 x 恰好變為 0 的最少操作次數。若無法達成,回傳 -1。
Example:
Input: nums = [1,1,4,2,3], x = 5 Output: 2(移除左邊兩個 [1,1] 或右邊兩個 [2,3])
Intuition#
核心思路:逆向思考 – 從兩端移除元素和為 x,等價於中間留下一段連續子陣列和為
totalSum - x。問題轉化為找和等於目標值的最長子陣列。
- 直接思考從兩端取元素很複雜(左取幾個、右取幾個的組合)
- 轉化後就是經典的「和等於 target 的最長子陣列」,用滑動視窗即可
- 最長中間子陣列 = 最少操作次數
Approaches#
1: Prefix Sum + HashMap#
- 概念: 用 Prefix Sum 找左邊的和,再從右邊找補足的部分
- 時間複雜度:
O(n) - 空間複雜度:
O(n)- HashMap
class Solution {
fun minOperations(nums: IntArray, x: Int): Int {
val total = nums.sum()
if (total < x) return -1
if (total == x) return nums.size
// 找左端 prefix sum
val leftSum = mutableMapOf(0 to 0)
var sum = 0
for (i in nums.indices) {
sum += nums[i]
if (sum <= x) leftSum[sum] = i + 1
}
var result = leftSum[x] ?: Int.MAX_VALUE
var rightSum = 0
for (j in nums.indices.reversed()) {
rightSum += nums[j]
if (rightSum > x) break
val need = x - rightSum
if (need in leftSum) {
val leftCount = leftSum[need]!!
if (leftCount <= j) { // 不重疊
result = minOf(result, leftCount + (nums.size - j))
}
}
}
return if (result == Int.MAX_VALUE) -1 else result
}
}⭐2: 逆向 Sliding Window(找最長中間子陣列)#
- 概念: 目標 =
totalSum - x,用可變滑動視窗找和等於目標的最長子陣列,答案 =n - maxLen - 時間複雜度:
O(n)- 一次遍歷 - 空間複雜度:
O(1)
class Solution {
fun minOperations(nums: IntArray, x: Int): Int {
val target = nums.sum() - x
if (target < 0) return -1
if (target == 0) return nums.size
var left = 0
var windowSum = 0
var maxLen = -1
for (right in nums.indices) {
windowSum += nums[right]
while (windowSum > target) {
windowSum -= nums[left]
left++
}
if (windowSum == target) {
maxLen = maxOf(maxLen, right - left + 1)
}
}
return if (maxLen == -1) -1 else nums.size - maxLen
}
}所有元素為正整數,所以視窗和具有單調性(擴大必增、縮小必減),滑動視窗才正確。
target < 0時直接回傳 -1。
🔑 Takeaways#
- Pattern: 逆向思考 – 把「兩端最少操作」轉化為「中間最長子陣列」
- 關鍵技巧: 「從兩端取」的問題常可轉化為「中間留下」,利用
totalSum - x作為目標值;正數陣列保證滑動視窗的單調性