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 是偶數。

來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/minimum-moves-to-make-array-complementary


解法一:樹狀陣列(區間更新,單點更新)

// 區間更新,單點求值
// 單點的值用字首和表示 即 c[i] = a[1] + a[2] + ... + a[i - 1] + a[i]
// 區間[x, y]增加k 即a[x] + k, a[y + 1] - k,這樣對於x->y的字首和都加k,y+1字首和並沒變
class BIT{
public:
    int n, m;
    vector<int> c;

    BIT(int _n) : n(_n),  c(_n + 1){
    }
    
    int lowbit(int x){
        return x & (-x);
    }
    //更新單點資訊
    void _update(int i, int k){
        while(i <= n){
            c[i] += k;
            i += lowbit(i);
        }
    }
    
    int getSum(int i){
        int res = 0;
        while(i > 0){
            res += c[i];
            i -= lowbit(i);
        }
        return res;
    }
    //區間[x, y]增加k
    void update(int x, int y, int k){
        _update(x, k);
        _update(y + 1, -k);
    }
};

class Solution {
public:
    int minMoves(vector<int>& nums, int limit) {
        BIT bit(limit * 2);
        int size = nums.size();
        for(int i = 0; i < size / 2; i++){
            int minNum = min(nums[i], nums[size - 1 - i]);
            int maxNum = max(nums[i], nums[size - 1 - i]);
            bit.update(1, minNum, 2);
            bit.update(maxNum + limit + 1, limit * 2, 2);
            bit.update(minNum + 1, maxNum + limit, 1);
            bit.update(nums[i] + nums[size - 1 - i],nums[i] + nums[size - 1 - i], -1);
        }
        int minCount = size;
        int minSum = 0;
        int a = *min_element(nums.begin(), nums.end());
        int b = *max_element(nums.begin(), nums.end());
        for(int i = a; i <= b + limit; i++){
            if(bit.getSum(i) < minCount){
                minCount = bit.getSum(i);
                minSum = i;
            }
        }
        int ans = 0;
        for(int i = 0; i < size / 2; i++){
            if(nums[i] + nums[size - 1 - i] == minSum)
                continue;
            int minNum = min(nums[i], nums[size - 1 - i]);
            int maxNum = max(nums[i], nums[size - 1 - i]);
            if(minNum + 1 > minSum || maxNum + limit < minSum)
                ans += 2;
            else
                ans += 1;
        }
        return ans;
    }
};

解法二:差分陣列

class Solution {
public:
    int minMoves(vector<int>& nums, int limit) {
        vector<int> count(limit * 2 + 2, 0);
        int size = nums.size();
        for(int i = 0; i < size / 2; i++){
            int a = max(nums[i], nums[size - 1 - i]);
            int b = min(nums[i], nums[size - 1 - i]);
            int sum = nums[i] + nums[size - 1 - i];
            // [0, b] and [a + limit + 1, limit * 2 + 1] need 2
            // [b + 1, sum - 1] and [sum + 1, a + limit] need 1
            count[0] += 2, count[b + 1] -= 2;
            count[a + limit + 1] += 2, count[limit * 2 + 1] -= 2;
            count[b + 1] += 1, count[sum] -= 1;
            count[sum + 1] += 1, count[a + limit + 1] -= 1;
        }
        int res = INT_MAX;
        for(int i = 1; i <= limit * 2; i++){
            count[i] += count[i - 1];
            if(count[i] < res)
                res = count[i];
        }
        return res;
    }
};

兩種方法底層的區間更新,單點查詢的原理都是差不多的。