• [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;
        }
    };
    

    这道题让我对树状数组的使用理解又加深了一些,后面可能会总结一下树状数组的使用和前缀和、差分、树状数组这些简单的数据结构的区别和功能。

  • 相关阅读:
    SAP全球企业官孙小群的生活智慧
    C++ vs Python向量运算速度评测
    C++ Error: no appropriate default constructor available
    危险的浮点数float
    Vagrant 手册之 Vagrantfile
    MySQL 服务器性能剖析
    Vagrant 手册之多个虚拟机 multi-machine
    Vagrant 手册之同步目录
    Vagrant 手册之同步目录
    MySQL 中的 information_schema 数据库
  • 原文地址:https://www.cnblogs.com/enmac/p/14062417.html
Copyright © 2020-2023  润新知