[LeetCode]1674. 使陣列互補的最少操作次數(掃描 + 差分\樹狀陣列)
阿新 • • 發佈:2020-11-30
1674. 使陣列互補的最少操作次數
LeetCode第217周賽的第三題,比賽時卡了一個小時,沒有想到O(n)的做法。對差分不熟悉,但是最關鍵的還是掃描的思路沒有想到。由於這道題有這麼幾個點比較重要,覺得應該特別記錄一下。
- 掃描:比賽時我也想到了當選定和K處於個個區間[2, lo]、[lo, hi]、[sum, sum]時的狀態。可是我把對於每個nums[i]的lo、hi、sum都看成獨立的一套。沒有聯絡起來。題解裡面把所有數放在數軸上的思路是我第一次見到這種思想。
- 差分:這題不一定一定要用差分,只是利用了差分的區間更新、單點查詢的性質。實際上也可也用樹狀陣列,或者更復雜的線段樹(還沒學過)。只是這裡差分就可以完成。
// // Created by root on 2020/11/30. // #include <vector> using namespace std; class Solution { public: // 差分陣列:b[k]。 // 差分陣列更新,對[l, r]區間++:b[l]++, b[r + 1]--; // 和為k時最小操作次數即b[0] + b[1] + ... + b[k] vector<int> b; void update(int l, int r, int x) { b[l] += x; b[r + 1] -= x; } int minMoves(vector<int>& nums, int limit) { b.resize(limit * 2 + 2); int n = nums.size(); for (int i = 0; i < n / 2; i++) { int lo = min(nums[i], nums[n - i - 1]) + 1; // 單步操作可以達到的最小值就是兩者之min + 1 int hi = max(nums[i], nums[n - i - 1]) + limit; // 最大值就是兩者之max + limit int sum = nums[i] + nums[n - i - 1]; // 當前和 // 想象一個數軸,K在移動: // lo <= K <= hi時,操作次數為1 // K = sum時,操作次數為0 // 其餘情況,操作次數為2,為方便起見,我們設初始步數就為2,這樣對其他操作我們只需要-- update(lo, hi, -1); // 對[lo, hi]中操作次數-- update(sum, sum, -1); // 對[sum, sum]中操作次數繼續-- } // 最大操作步數為 n / 2 * 2 int now = n; int res = n; // 陣列互補時,選中的和K的範圍[2, limit * 2 + 2] for (int i = 2; i < limit * 2 + 2; i++) { now += b[i]; res = min(res, now); } return res; } };
附上樹狀陣列解法:
// 樹狀陣列解法:區間更新、單點查詢 class BIT { public: vector<int> trees; int max_n; BIT(int n) { trees.resize(n); max_n = n; } int lowBit(int x) { return x & (-x); } void update(int pos, int x) { for (int i = pos; i < max_n; i += lowBit(i)) { trees[i] += x; } } int query(int pos) { int res = 0; for (int i = pos; i; i -= lowBit(i)) { res += trees[i]; } return res; } // 引入差分,使樹狀陣列可以進行區間更新 void update(int l, int r, int x) { update(l, x); update(r + 1, -x); } }; class Solution2 { public: int minMoves(vector<int>& nums, int limit) { BIT bit(limit * 2 + 2); int n = nums.size(); for (int i = 0; i < n / 2; i++) { int lo = min(nums[i], nums[n - i - 1]) + 1; int hi = max(nums[i], nums[n - i - 1]) + limit; int sum = nums[i] + nums[n - i - 1]; bit.update(lo, hi, -1); bit.update(sum, sum, -1); } // 由於樹狀陣列的區間查詢不需要遍歷,因此這裡now直接記錄K == i時,減去的運算元。因此,答案需要返回n + res int now = 0; int res = n; // 陣列互補時,選中的和K的範圍[2, limit * 2 + 2] for (int i = 2; i < limit * 2 + 2; i++) { now = bit.query(i); res = min(res, now); } return n + res; } };
這道題讓我對樹狀陣列的使用理解又加深了一些,後面可能會總結一下樹狀陣列的使用和字首和、差分、樹狀陣列這些簡單的資料結構的區別和功能。