将数组清空

给你一个包含若干互不相同整数的数组nums,你需要执行以下操作直到数组为空 :

  • 如果数组中第一个元素是当前数组中的最小值则删除它
  • 否则,将第一个元素移动到数组的末尾

请你返回需要多少个操作使nums为空

一. 数学思维减小问题规模

除去删除元素的操作外,考虑每个数需要移动的次数
假设数组按升序排列,则不需要移动

以123765489为例
通过三个删除操作变成765489
对于下一个要删除的4来说,4是不需要任何移动的,因为它与前面删除的3是升序关系
而对于要删除的5来说,因为它与4逆序,必然有一次后移操作
在删除4的时候变成89765,这时765皆后移一次,删除5的时候,存在中间状态76589,此时89也后移一次
可以发现当存在逆序的时候,所有数据都会在发生一次移动后达到原来相对位置的中间状态
相对位置便不会因为移动而发生变化,从而为下一次处理做铺垫

当前待删除数出现在了已删除数的前面(逆序)
在删除这对逆序对前一个元素的过程中,会让其他元素移动一次,恢复到原来的相对位置状态

class Solution {
public:
    long long countOperationsToEmptyArray(vector<int>& nums) {
        map<int, int> m;
        for(int i = 0; i < nums.size(); i++)
            m[nums[i]] = i;//记录值对应下标
        int N = 1;//删除元素一次操作

        long long res = 0;
        auto it = m.begin();//最小值
        res = res + N;
        
        int index = it->second;//记录之前位置
        it++;
        while(it != m.end()){//从小到大遍历所有数
            if(it->second<index)//比之前坐标小(逆序)
                N++;//所有元素操作次数加一,恢复到原来的相对位置状态
            res = res + N;
            index = it->second;
            it++;
        }
        return res;   
    }
};

二. 树状数组

想象成用一个下标反复遍历数组,考虑下标的总共移动次数
关键在于寻找下一个最小值位置,以及跳过途中已删除的元素(不记次数)
前者可以用排序或红黑树,后者用树状数组或线段树

class Solution {
public:
    vector<int> tree;//树状数组
    int n;
    long long countOperationsToEmptyArray(vector<int> &nums) {
        n = nums.size();
        map<int, int> m;
        for(int i = 0; i < nums.size(); i++)
            m[nums[i]] = i;//记录值对应下标

        long long res = n; // 先把 n 计入答案
        tree.resize(n+1);//为了统一操作防止越界,特别是查询操作,多分配一个空间

        int pre = 1; // 上一个最小值的位置,初始为 1
        auto it = m.begin();
        for (int k = 0; k < n; k++,it++) {//顺序访问当前最小值
            int i = it->second + 1; // 下标从 1 开始
            if (i >= pre) // 从 pre 移动到 i,跳过已经删除的数
                res += i - pre - query(pre, i);
            else // 从 pre 移动到 n,再从 1 移动到 i,跳过已经删除的数
                res += n - pre + i - k + query(i, pre - 1);
            updata(i,1); // 删除该数,使得已删数加一
            pre = i;
        }
        return res;
    }

    int lowbit(int x){//求二进制化最后一位的值
        return x&(-x);
    }
    void updata(int i,int k){ //在i位置加上k,O(logn)复杂度单点修改
        while(i<=n){//更新子树上所有值
            tree[i]+=k;
            i+=lowbit(i);//移动到父亲节点
        }
    }

    long long sum(int i){  //求数组前i项的和
        long long res=0;
        while(i>0){//O(logn)求前缀和
            res+=tree[i];
            i-=lowbit(i);//移动到前一棵子树(子区间)
        }
        return res;
    }
    long long query(int left, int right) {
        return sum(right) - sum(left - 1);
    }

};
posted @ 2023-04-30 14:33  失控D大白兔  阅读(65)  评论(0编辑  收藏  举报