1. 程式人生 > 實用技巧 >[LeetCode]1674. 使陣列互補的最少操作次數(掃描 + 差分\樹狀陣列)

[LeetCode]1674. 使陣列互補的最少操作次數(掃描 + 差分\樹狀陣列)

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

​ LeetCode第217周賽的第三題,比賽時卡了一個小時,沒有想到O(n)的做法。對差分不熟悉,但是最關鍵的還是掃描的思路沒有想到。由於這道題有這麼幾個點比較重要,覺得應該特別記錄一下。

  1. 掃描:比賽時我也想到了當選定和K處於個個區間[2, lo]、[lo, hi]、[sum, sum]時的狀態。可是我把對於每個nums[i]的lo、hi、sum都看成獨立的一套。沒有聯絡起來。題解裡面把所有數放在數軸上的思路是我第一次見到這種思想。
  2. 差分:這題不一定一定要用差分,只是利用了差分的區間更新、單點查詢的性質。實際上也可也用樹狀陣列,或者更復雜的線段樹(還沒學過)。只是這裡差分就可以完成。
//
// 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;
    }
};

這道題讓我對樹狀陣列的使用理解又加深了一些,後面可能會總結一下樹狀陣列的使用和字首和、差分、樹狀陣列這些簡單的資料結構的區別和功能。