树状数组

树状数组

1. 树状数组原理

原理

  • 树状数组可以有两个操作:(1)快速求数组A前缀和;(2)修改数组A中某个数据。
  • 上面两个操作的时间复杂度都是O(log(N))的

注意:下面所有的数组下标都是从1开始考虑的!!!

如果我们想求出A[1…x]区间和,我们可以根据x的二进制表示将该区间划分为若干部分,如果x的二进制表示如下:
x = 2 i k + 2 i k − 1 + . . . + 2 i 1 i k > i k − 1 > . . . > i 1 x = 2^{i_k} + 2^{i_{k-1}} + ... + 2^{i_1} \quad \quad i_k > i_{k-1}>...>i_1 x=2ik+2ik1+...+2i1ik>ik1>...>i1
则可以将A[1…x]划分为如下区间:
A ( x − 2 i 1 , . . . , x ] 该 区 间 包 含 2 i 1 个 数 据 A ( x − 2 i 1 − 2 i 2 , . . . , x − 2 i 1 ] 该 区 间 包 含 2 i 2 个 数 据 . . A ( 0 , . . . , x − 2 i 1 − 2 i 2 − . . . − 2 i k − 1 ] 该 区 间 包 含 2 i k 个 数 据 A(x-2^{i_1},..., x] \quad \quad 该区间包含2^{i_1}个数据 \\ A(x-2^{i_1}-2^{i_2},..., x-2^{i_1}] \quad \quad 该区间包含2^{i_2}个数据 \\ . \\ . \\ A(0,..., x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}] \quad \quad 该区间包含2^{i_k}个数据 \\ A(x2i1,...,x]2i1A(x2i12i2,...,x2i1]2i2..A(0,...,x2i12i2...2ik1]2ik
假设这样的区间我们已经知道了,那么我们在O(log(x))的时间里就可以将A[1…x]区间和求出来。

注意到对于上面被划分出来的各个区间,有: 2 i 1 2^{i_1} 2i1 是x的二进制表示的最后一位1; 2 i 2 2^{i_2} 2i2 x − 2 i 1 x-2^{i_1} x2i1 的二进制表示的最后一位1;以此类推。因此上述区间又可以写成如下通式,记为tr[R]:
t r [ R ] = A [ R − l o w b i t ( x ) + 1 ,   R ] tr[R]=A[R-lowbit(x)+1,\ R] tr[R]=A[Rlowbit(x)+1, R]
所以有:
A [ 1... x ] = t r [ x ] + t r [ x − l o w b i t ( x ) ] + t r [ y − l o w b i t ( y ) ] + . . . 其 中 y = x − l o w b i t ( x ) A[1...x] = tr[x] + tr[x-lowbit(x)]+tr[y-lowbit(y)]+... \quad \quad 其中y=x-lowbit(x) A[1...x]=tr[x]+tr[xlowbit(x)]+tr[ylowbit(y)]+...y=xlowbit(x)
在这里插入图片描述

我们可以把tr看成一棵树,那么现在有两个问题:(1)如何找到某个节点的所有儿子;(2)如何找到某个节点的父亲。

(1)寻找tr[x]的儿子(初始化中会用到):A[x],tr[x-1],tr[x-1-lowbit(x-1)],…。如果x=0b…100…000(这里的1是从左往右最后一个1),儿子对应二进制就是:A[x],tr[0b…011…111],tr[0b…011…110],…,tr[0b…010…000]

(2)寻找tr[t]的父亲(更新数据(add)时会用到):tr[t+lowbit(t)]。这个可以由(1)看出来,如果父节点x=0b…100…000,子节点必定是t=0b…011…这种形式(1是连续的),由t可以反向可以推出来x,即x = t + lowbit(t)。

代码模板

int n;  // 数组中元素个数
int a[N];  // 原数组,a[1...n]为有效元素
int tr[N];

int lowbit(int x) {
    return x & -x;
}

// 更新,操作原数组让a[x] += c;
// 对应上图中自下而上的操作
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] +=c;
}

// 查询,获得a[1...x]的区间和
int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];  // 0b1100=0b0100+0b1000
    return res;
}

// 树状数组的初始化,有很多方式,如下
// 时间复杂度:O(n*log(n))
void init() {
    for (int i = 1; i <= n; i++) add(i, a[i]);
}
// 时间复杂度:O(n)
// 对应上图中自上而下的操作
void init() {
    for (int i = 1; i <= n; i++) {  // 求tr[i]
        tr[i] = a[i];  // a[i]也是tr[i]的孩子
        for (int j = i - 1; j > i - lowbit(i); j -= lowbit(j))  // 遍历tr[i]的孩子
            tr[i] += tr[j];
    }
}
// 另外还可以根据tr的定义去算,先求出a的前缀和s,然后求出tr:tr[x] = s[x] - s[x-lowbit(x)]。时间复杂度:O(n)

2. AcWing上的树状数组题目

AcWing 0241. 楼兰图腾

问题描述

分析

  • 因为 y 1 y_1 y1~ y n y_n yn 是1~n的一个排列,所以不需要进行离散化了;原数组A的下标是 y 1 y_1 y1~ y n y_n yn 这些数据,数值表示这些数字的个数,非常类似于计数排序的做法。
  • 以求解V字形为例,对于我们考察的每个数据 y i y_i yi,我们只要统计出其左侧和其右侧各有多少个数据大于它,然后使用乘法原理就能得到以 y i y_i yi为"山谷"的数量 s i s_i si,然后将所有的 s i s_i si相加就是所有V的个数。
  • 分析最多有多少答案,对于每个 y i y_i yi,其左右数据够大于它,最坏情况下会有 1 0 5 × 1 0 5 × 2 × 1 0 5 = 2 × 1 0 15 10^5 \times 10^5 \times 2 \times 10^5=2 \times 10^{15} 105×105×2×105=2×1015个答案,超过int的范围,因此需要使用long long来存储答案。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 200010;

int n;
int a[N];  // a[y] = 1; 表示y这个数据有1个
int tr[N];
// Greater[k]表示a[1...k-1]中大于a[k]的数据的数量
// lower[k]表示a[1...k-1]中小于a[k]的数据的数量
int Greater[N], lower[N];

int lowbit(int x) {
    return x & -x;
}

// 操作原数组,让a[x] += c;
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] +=c;
}

// 获得a[1...x]的区间和
int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main() {
    
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; i++) {
        int y = a[i];
        Greater[i] = sum(n) - sum(y);  // 左侧大于y的数的数量
        lower[i] = sum(y - 1);  // 左侧小于y的数的数量
        add(y, 1);
    }
    
    memset(tr, 0, sizeof tr);
    LL res1 = 0, res2 = 0;
    for (int i = n; i; i--) {
        int y = a[i];
        res1 += Greater[i] * (LL)(sum(n) - sum(y));
        res2 += lower[i] * (LL)(sum(y - 1));
        add(y, 1);
    }
    
    printf("%lld %lld\n", res1, res2);
    
    return 0;
}

AcWing 242. 一个简单的整数问题

问题描述

分析

  • 对于最原始的树状数组存在两个操作:单点加,求区间和(即a[x]+=c, query[L~R]);

  • 本题中的操作正好反过来:区间加,求单点和(即a[L~R]+=c, query[x]);

  • 这一题的做法是使用差分的思想,将原数组a转化成差分数组b,则:

    (1) a [ L , R ] + = c    ⟺    b [ L ] + = c , b [ R + 1 ] − = c a[L,R]+=c \iff b[L]+=c, b[R+1]-=c a[LR]+=cb[L]+=c,b[R+1]=c

    (2) a [ x ] = ∑ b [ i ] , 1 ≤ i ≤ x a[x] = \sum b[i], 1 \le i \le x a[x]=b[i],1ix

  • 如何将原数组a转换为差分数组呢?转化过程如下,这里必须要求数据从a[1]开始,a[0]=0:

b [ 1 ] = a [ 1 ] − a [ 0 ] b [ 2 ] = a [ 2 ] − a [ 1 ] . . . b [ n ] = a [ n ] − a [ n − 1 ] b[1] = a[1] - a[0] \\ b[2] = a[2] - a[1] \\ ... \\ b[n] = a[n] - a[n - 1] b[1]=a[1]a[0]b[2]=a[2]a[1]...b[n]=a[n]a[n1]

  • 这样,对于数组a的区间加法可以转化成对数组b的单点加;对数组a求单点和可以转化成对数组b的区间和,就可以使用树状数组的操作了。

  • 另外数组a中的数据最大为 1 0 9 10^9 109,操作次数为 1 0 5 10^5 105,每次最大加上1000,不会超过int的范围,因此需要使用long long存储结果。

代码

  • C++
#include <iostream>
#include <cstring>

using namespace std;

const int N = 100010;

int n, m;  // 数列长度、操作个数
int a[N];  // 原数组
int tr[N];

int lowbit(int x) {
    return x & -x;
}

// 操作原数组,让b[x] += c;
// 这里的b是差分数组
void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

// 查询,获得b[1...x]的区间和
int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main() {
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    for (int i = 1; i <= n; i++) add(i, a[i] - a[i - 1]);  // 使用差分数组初始化
    
    while (m--) {
        char op[2];
        int l, r, d;
        scanf("%s%d", op, &l);
        if (*op == 'C') {
            scanf("%d%d", &r, &d);
            add(l, d), add(r + 1, -d);
        } else {
            printf("%d\n", sum(l));
        }
    }
    
    return 0;
}

AcWing 243. 一个简单的整数问题2

问题描述

分析

  • 对于最原始的树状数组存在两个操作:单点加,求区间和(即a[x]+=c, query[L~R]);

  • AcWing 242. 一个简单的整数问题的操作正好反过来:区间加,求单点和(即a[L~R]+=c, query[x]);

  • 本题的操作更进一步:区间加,求区间和(即a[L~R]+=c, query[L~R]);

  • 对于区间加,我们仍然可以使用差分的思想,将原数组a转化成差分数组b,则:

    (1) a [ L , R ] + = c    ⟺    b [ L ] + = c , b [ R + 1 ] − = c a[L,R]+=c \iff b[L]+=c, b[R+1]-=c a[LR]+=cb[L]+=c,b[R+1]=c

    (2) a [ x ] = ∑ b [ i ] , 1 ≤ i ≤ x a[x] = \sum b[i], 1 \le i \le x a[x]=b[i],1ix

  • 如何将原数组a转换为差分数组呢?转化过程如下,这里必须要求数据从a[1]开始,a[0]=0:

b [ 1 ] = a [ 1 ] − a [ 0 ] b [ 2 ] = a [ 2 ] − a [ 1 ] . . . b [ n ] = a [ n ] − a [ n − 1 ] b[1] = a[1] - a[0] \\ b[2] = a[2] - a[1] \\ ... \\ b[n] = a[n] - a[n - 1] b[1]=a[1]a[0]b[2]=a[2]a[1]...b[n]=a[n]a[n1]

  • 这样,对于数组a的区间加法可以转化成对数组b的单点加;但是对数组a求区间和我们就要考虑一下如何求解了。
  • 求数组a的区间和,只需要求出数组a的前缀和即可,即求出:

∑ i = 1 x a i \sum_{i=1}^{x} a_i i=1xai

又因为: a [ i ] = ∑ b [ j ] , 1 ≤ j ≤ i a[i] = \sum b[j], 1 \le j \le i a[i]=b[j],1ji,所以有:
∑ i = 1 x a i = ∑ i = 1 x ∑ j = 1 i b i = ( b 1 ) + ( b 1 + b 2 ) + . . . + ( b 1 + b 2 + . . . + b x ) \sum_{i=1}^{x} a_i = \sum_{i=1}^{x} \sum_{j=1}^{i} b_i = (b_1)+(b_1+b_2)+...+(b_1+b_2+...+b_x) i=1xai=i=1xj=1ibi=(b1)+(b1+b2)+...+(b1+b2+...+bx)
如下图(蓝色的是我们需要求解的部分,红色的是我们补上的内容,则蓝色和=全部和-红色和):

在这里插入图片描述

则有:
∑ i = 1 x a i = ( ∑ i = 1 x b i ) × ( x + 1 ) − ( b 1 + 2 × b 2 + . . . + x × b x ) \sum_{i=1}^{x} a_i = \Bigl(\sum_{i=1}^{x} b_i \Bigr) \times (x+1) - (b1 + 2 \times b_2 + ... + x \times b_x) i=1xai=(i=1xbi)×(x+1)(b1+2×b2+...+x×bx)
因此,我们在操作的同时维护两个前缀和即可,分别是: ∑ b i \sum b_i bi ∑ i × b i \sum i \times b_i i×bi

  • 另外数组a中的数据最大为 1 0 9 10^9 109,操作次数为 1 0 5 10^5 105,每次最大加上1000,因为是区间和,所以可能会超过int的范围,因此需要使用long long存储结果。

代码

  • C++
#include <iostream>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;  // 数列长度、操作个数
int a[N];  // 原数组
LL tr1[N];  // 维护a对应的差分数组b的前缀和
LL tr2[N];  // 维护b[i] * i的前缀和

int lowbit(int x) {
    return x & -x;
}

void add(LL tr[], int x, LL c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

LL sum(LL tr[], int x) {
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

LL prefix_sum(int x) {
    return sum(tr1, x) * (x + 1) - sum(tr2, x);
}

int main() {
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    
    // 使用差分数组b对树状数组初始化
    for (int i = 1; i <= n; i++) {
        int b = a[i] - a[i - 1];
        add(tr1, i, b);
        add(tr2, i, (LL)b * i);
    }
    
    while (m--) {
        char op[2];
        int l, r, d;
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'C') {
            scanf("%d", &d);
            // b[l] += d, tr2维护的是b[i] * i的前缀和
            // 因此b[l]增加d, 则(b[l] + d) * l增加了l*d
            add(tr1, l, d), add(tr2, l, l * d);
            // b[r + 1] -= d
            add(tr1, r + 1, -d), add(tr2, r + 1, (r + 1) * -d);
        } else {
            printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
        }
    }
    
    return 0;
}

AcWing 244. 谜一样的牛

问题描述

分析

  • 由题目可知,这n有牛的身高是1~n的一个排列。

  • 我们可以从排在最后的一头牛开始考虑,假设 h i h_i hi表示第i头牛前面有 h i h_i hi头牛比它矮,则当考虑 h n h_n hn时,表示第n头牛应该是第 h n + 1 h_n+1 hn+1矮的牛;因此当考虑第 h i h_i hi头牛是,它应该是剩余高度中第 h i + 1 h_i+1 hi+1矮的牛(所谓剩余是指已经确定高度的数据不能参与进来)。

  • 考虑上面的分析中存在什么操作:

    (1)从剩余的数中,找出第K小的数;

    (2)删除某个数;

  • 这两个操作是平衡树的两个典型操作,但是平衡树很难写,我们考虑使用树状数组解决。

  • 我们可以使用一个数组记录每个数据是否被使用过,记为数组a, a [ i ] = = 1 a[i]==1 a[i]==1表示高度i没有被使用过, a [ i ] = = 0 a[i]==0 a[i]==0表示已经存在某头牛的高度为i;设 s u m ( x ) = ∑ a [ i ] , 1 ≤ i ≤ x sum(x)=\sum a[i], 1 \le i \le x sum(x)=a[i],1ix则上面的操作等价于:

    (1)找到一个最小的x,使得sum(x)==K;

    (2)将a[x]置为0,代表该高度已经被使用过。

  • 第(2)个操作相当于单点加,第(1)个操作相当于区间和,可以使用树状数组解决。

  • 对于第(1)个操作,因为sum(x)是x的一个单调递增函数,因此可以使用二分求解。

代码

  • C++
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int h[N];  // h[i]表示前面有h[i]头牛比当前牛矮
int ans[N];  // 最终这一列牛的高度
int tr[N];  // 维护上面分析中的数组a的前缀和, 我们并不需要将a定义出来

int lowbit(int x) {
    return x & -x;
}

void add(int x, int c) {
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int sum(int x) {
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main() {
    
    scanf("%d", &n);
    for (int i = 2; i <= n; i++) scanf("%d", &h[i]);
    
    // 初始化树状数组, 因为开始a中所有元素都为1
    // 所以根据tr[i]的定义,即tr[i] = a[i - lowbit(i) + 1, i]之和
    // tr[i] = lowbit(i)
    for (int i = 1; i <= n; i++) tr[i] = lowbit(i);
    
    for (int i = n; i; i--) {
        int k = h[i] + 1;  // 此时第i头牛的高度是所有牛当中第k小的
        int l = 1, r = n;
        while (l < r) {
            int mid = l + r >> 1;
            if (sum(mid) >= k) r = mid;
            else l = mid + 1;
        }
        ans[i] = r;
        add(r, -1);  // 让a[i]变为0
    }
    
    for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
    
    return 0;
}

3. 力扣上的树状数组的题目

Leetcode 0307 区域和检索 - 数组可变

问题描述

代码

  • C++
class NumArray {
public:
    int n;
    vector<int> tr, nums;

    int lowbit(int x) {
        return x & -x;
    }

    void add(int x, int v) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
    }

    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += tr[i];
        return res;
    }

    NumArray(vector<int> &_nums) {

        nums = _nums;
        n = nums.size();
        tr.resize(n + 1);

        // 线性初始化
        for (int i = 1; i <= n; i++) {
            tr[i] = nums[i - 1];
            for (int j = i - 1; j > i - lowbit(i); j -= lowbit(j))
                tr[i] += tr[j];
        }
    }

    void update(int i, int val) {
        add(i + 1, val - nums[i]);
        nums[i] = val;
    }

    int sumRange(int i, int j) {
        i++, j++;
        return query(j) - query(i - 1);
    }
};

Leetcode 0315 计算右侧小于当前元素的个数

问题描述

代码

  • C++
class Solution {
public:
    int n = 20001;
    vector<int> tr;

    int lowbit(int x) {
        return x & -x;
    }

    void add(int x, int v) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
    }

    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += tr[i];
        return res;
    }

    vector<int> countSmaller(vector<int> &nums) {
        tr.resize(n + 1);

        vector<int> res(nums.size());
        for (int i = nums.size() - 1; i >= 0; i--) {
            int x = nums[i] + 10001;
            res[i] = query(x - 1);
            add(x, 1);
        }
        return res;
    }
};
  • Java
public class Solution {

    public static final int N = 20001;
    static int[] tr = new int[N + 1];

    private int lowbit(int x) {
        return x & -x;
    }

    private void add(int x, int v) {
        for (int i = x; i <= N; i += lowbit(i)) tr[i] += v;
    }

    private int query(int x) {
        int res = 0;
        for (int i = x; i > 0; i -= lowbit(i)) res += tr[i];
        return res;
    }

    public List<Integer> countSmaller(int[] nums) {

        Arrays.fill(tr, 0);  // 必须加上,否则lc无法通过
        List<Integer> res = new ArrayList<>();
        for (int i = nums.length - 1; i >= 0; i--) {
            int x = nums[i] + 10001;
            res.add(query(x - 1));
            add(x, 1);
        }
        Collections.reverse(res);
        return res;
    }
}

Leetcode 0327 区间和的个数

问题描述

代码

  • C++
class Solution {
public:
    int m;
    vector<int> tr;  // 树状数组
    vector<LL> all;  // 待离散化的数据

    int get(LL x) {
        return lower_bound(all.begin(), all.end(), x) - all.begin() + 1;
    }

    int lowbit(int x) {
        return x & -x;
    }

    void add(int x, int v) {
        for (int i = x; i <= m; i += lowbit(i)) tr[i] += v;
    }

    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i))res += tr[i];
        return res;
    }

    int countRangeSum(vector<int> &nums, int lower, int upper) {

        int n = nums.size();
        vector<LL> s(n + 1);  // 前缀和
        all.push_back(0);
        for (int i = 1; i <= n; i++) {
            s[i] = s[i - 1] + nums[i - 1];
            all.push_back(s[i]);
            all.push_back(s[i] - lower);
            all.push_back(s[i] - upper - 1);
        }
        sort(all.begin(), all.end());
        all.erase(unique(all.begin(), all.end()), all.end());
        m = all.size();
        tr.resize(m + 1);

        int res = 0;
        // 为什么有下面这句话:https://www.acwing.com/activity/content/code/content/477194/
        // 相当于考虑分析中的 sj = 0 --> lower <= si <= upper 这种情况
        add(get(0), 1);
        for (int i = 1; i <= n; i++) {
            res += query(get(s[i] - lower)) - query(get(s[i] - upper - 1));
            add(get(s[i]), 1);
        }
        return res;
    }
};

Leetcode 0406 根据身高重建队列

问题描述

分析

  • 这一题存在两种做法。

方法1

  • 0~n-1,一共n个位置,我们需要考虑每个人放在哪个位置。

  • 将people按照第一维升序、第二维降序的顺序排序,然后依次从前往后考虑每一个二元组。

  • 假设当前考察的是第i个二元组 ( h , k ) (h, k) (h,k),假设第i个二元组的身高严格大于第i-1个二元组的身高,则我们需要在还没有放入人的位置中找到第k+1位置对应的下标,然后将第i个人(二元组)放到这个位置。

  • 假设第i个二元组的身高等于第i-1个二元组的身高,则我们仍按照上面的方式操作,结果仍然正确,因为此时第i个二元组一定放在第i-1个二元组左边。第i-1个人左边大于等于他的人数没有改变(因为等于也符合条件)。

  • 考虑上面的分析中存在什么操作(类似于AcWing 244. 谜一样的牛):

    (1)从剩余的数(对应的位置索引)中,找出第K小的数;

    (2)删除某个数(索引);

  • 我们可以使用一个数组记录每个数据是否被使用过,记为数组a, a [ i ] = = 0 a[i]==0 a[i]==0表示位置i没有被使用过, a [ i ] = = 1 a[i]==1 a[i]==1表示位置i已经被人占了;设 s u m ( x ) = ∑ a [ i ] , 1 ≤ i ≤ x sum(x)=\sum a[i], 1 \le i \le x sum(x)=a[i],1ix则上面的操作等价于(代码实现中a不需要定义出来):

  • 则上面的操作等价于:

    (1)找到一个最小的x,使得sum(x)==K;

    (2)将a[x]置为1,代表该高度已经被使用过。

  • 第(2)个操作相当于单点加,第(1)个操作相当于区间和,可以使用树状数组解决。

  • 对于第(1)个操作,因为sum(x)是x的一个单调递减函数,因此可以使用二分求解。

方法2

  • 将people按照第一维降序、第二维升序的顺序排序,然后依次从前往后考虑每一个二元组。
  • 假设记录答案的数组为res,对于第i个二元组 ( h , k ) (h, k) (h,k),将其插入res[k]位置即可。

在这里插入图片描述

  • 上图参考的网址:网址

代码

  • C++
// 方法1
/**
 * 执行用时:104 ms, 在所有 C++ 提交中击败了76.69%的用户
 * 内存消耗:23.9 MB, 在所有 C++ 提交中击败了24.85%的用户
 */
class Solution {
public:
    int n;
    vector<int> tr;

    int lowbit(int x) {
        return x & -x;
    }

    void add(int x, int c) {
        for (int i = x; i <= n; i += lowbit(i)) tr[i] += c;
    }

    int query(int x) {
        int res = 0;
        for (int i = x; i; i -= lowbit(i)) res += tr[i];
        return res;
    }

    vector<vector<int>> reconstructQueue(vector<vector<int>> &people) {
        n = people.size();
        tr.resize(n + 1);  // 树状数组下标必须从1开始

        sort(people.begin(), people.end(), [](vector<int> a, vector<int> b) {
            if (a[0] != b[0]) return a[0] < b[0];  // 按照第一维升序
            return a[1] > b[1];  // 按照第二维降序
        });

        vector<vector<int>> res(n);
        for (auto p : people) {
            int h = p[0], k = p[1];
            int l = 1, r = n;
            while (l < r) {
                int mid = l + r >> 1;
                // query(mid)返回a[1..mid]中1的个数,0表示该位置没被占用
                if (mid - query(mid) >= k + 1) r = mid;
                else l = mid + 1;
            }
            res[r - 1] = p;  // a[1]表示第0个位置的占用情况
            add(r, 1);
        }
        return res;
    }
};
// 方法二
/**
 * 执行用时:200 ms, 在所有 C++ 提交中击败了35.38%的用户
 * 内存消耗:11.8 MB, 在所有 C++ 提交中击败了70.56%的用户
 */
class Solution {
public:
    vector<vector<int>> reconstructQueue(vector<vector<int>> &people) {

        // 按照第一维降序,第二维升序排列
        sort(people.begin(), people.end(), [](const vector<int> &a, const vector<int> &b) {
            if (a[0] == b[0]) return a[1] < b[1];
            return a[0] > b[0];
        });

        vector<vector<int>> res;
        for (auto &p : people) {
            res.insert(res.begin() + p[1], p);
        }

        return res;
    }
};
  • Java
// 方法二
/**
 * Date: 2020/11/16 8:59
 * Content: 按照身高降序 K升序排序;然后将排序后数据依次插入list,先插入的数据一定是比较高的人
 * 执行用时:8 ms, 在所有 Java 提交中击败了90.65%的用户
 * 内存消耗:39.5 MB, 在所有 Java 提交中击败了75.50%的用户
 */
public class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, (o1, o2) -> {
            // 如果身高相等(o1[0] == o2[0]) , 按照K降序排列(o2[0] - o1[0])
            return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
        });

        List<int[]> list = new ArrayList<>();
        for (int[] p : people) {
            list.add(p[1], p);
        }

        return list.toArray(new int[0][0]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值