1. 程式人生 > 實用技巧 >leetcode#1674. 使陣列互補的最少操作次數(差分陣列)

leetcode#1674. 使陣列互補的最少操作次數(差分陣列)

  1. 使陣列互補的最少操作次數

給你一個長度為 偶數 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
}