leetcode#1674. 使陣列互補的最少操作次數(差分陣列)
- 使陣列互補的最少操作次數
給你一個長度為 偶數 n 的整數陣列 nums 和一個整數 limit 。每一次操作,你可以將 nums 中的任何整數替換為 1 到 limit 之間的另一個整數。
如果對於所有下標 i(下標從 0 開始),nums[i] + nums[n - 1 - i] 都等於同一個數,則陣列 nums 是 互補的 。例如,陣列 [1,2,3,4] 是互補的,因為對於所有下標 i ,nums[i] + nums[n - 1 - i] = 5 。
返回使陣列 互補 的 最少 操作次數。
示例 1:
輸入:nums = [1,2,4,3], limit = 4
輸出:1
解釋:經過 1 次操作,你可以將陣列 nums 變成 [1,2,2,3](加粗元素是變更的數字):
nums[0] + nums[3] = 1 + 3 = 4.
nums[1] + nums[2] = 2 + 2 = 4.
nums[2] + nums[1] = 2 + 2 = 4.
nums[3] + nums[0] = 3 + 1 = 4.
對於每個 i ,nums[i] + nums[n-1-i] = 4 ,所以 nums 是互補的。
示例 2:
輸入:nums = [1,2,2,1], limit = 2
輸出:2
解釋:經過 2 次操作,你可以將陣列 nums 變成 [2,2,2,2] 。你不能將任何數字變更為 3 ,因為 3 > limit 。
示例 3:
輸入:nums = [1,2,1,2], limit = 2
輸出:0
解釋:nums 已經是互補的。
提示:
n == nums.length
2 <= n <= 105
1 <= nums[i] <= limit <= 105
n 是偶數。
https://leetcode-cn.com/problems/minimum-moves-to-make-array-complementary/
思路:
接下來我們舉幾個例子尋找一下規律:
假設陣列是1,3,4,2。limit = 5,則第一個數對為(1,2)
我們找出每一個數對的最大值max和最小值min。
如圖中數對,將該數對和轉化成3所需要的操作次數顯然是0,接下來還要討論轉化次數為1和轉化次數為2的情況。
顯然轉化1次能取到的最小值是min + 1,能取到的最大值是max + limit。那麼在這範圍之外的就是需要轉化次數2次。
所以對於每一對數對:分如下幾種情況
在[2, min]這個區間,arr[i] += 2;
在[min + 1, min + max]區間,arr[i] += 1;
在min + max上,arr[i] += 0;
在[min + max + 1, max + limit]區間上,arr[i] += 1;
在[max + limit + 1, limit + limit]區間上,arr[i] += 2;
具體操作
-
先將 [2, 2 * limit] 的範圍需要的運算元 + 2;
-
之後,將 [1 + min(A, B), limit + max(A, B)] 的範圍需要的運算元 - 1(即 2 - 1 = 1,操作 1 次);
-
之後,將 [A + B] 位置的值再 -1(即 1 - 1 = 0,操作 0 次)。
仔細觀察,我們發現,我們只需要作區間更新,和單點查詢。
對於這個需求,有一種非常常規的”資料結構“,叫差分陣列,完全滿足需求,並且程式設計極其簡單,整體可以在 O(n) 的時間解決。
打上引號,是因為差分陣列就是一個數組而已
關於差分陣列
簡單來說,差分陣列 diff[i],儲存的是 res[i] - res[i - 1];而差分陣列 diff[0...i] 的和,就是 res[i] 的值。
大家可以用一個小資料試驗一下,很好理解。
如果我們想給 [l, r] 的區間加上一個數字 a, 只需要 diff[l] += a,diff[r + 1] -= a。
這樣做,diff[0...i] 的和,就是更新後 res[i] 的值。
依然是,大家可以用一個小資料試驗一下,其實很好理解。
這裡不贅述,更嚴謹的分析看上面這個文件:https://oi-wiki.org/basic/prefix-sum/#_6
程式碼:
func minMoves(nums []int, limit int) int {
n := len(nums)
// 差分陣列, diff[0...x] 的和表示最終互補的數字和為 x,需要的運算元
// 因為差分陣列的計算需要更新 r + 1,所以陣列的總大小在 limit * 2 + 1 的基礎上再 + 1
diff := make([]int, 2*limit+2)
for i := 0; i < n/2; i++ {
a, b := nums[i], nums[n-1-i]
// [2, 2 * limit] 範圍 + 2
l, r := 2, 2*limit
diff[l] += 2
diff[r+1] -= 2
// [1 + min(A, B), limit + max(A, B)] 範圍 -1
l, r = min(a, b)+1, max(a, b)+limit
diff[l] -= 1
diff[r+1] += 1
// [A + B] 再 -1
l, r = a+b, a+b
diff[l] -= 1
diff[r+1] += 1
}
// 依次求和,得到 最終互補的數字和 i 的時候,需要的運算元 sum
// 取最小值
sum, ret := 0, n
for i := 2; i <= 2*limit; i++ {
sum += diff[i]
if sum < ret {
ret = sum
}
}
return ret
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}