📌  相关文章
📜  数组中每个元素右侧的较大元素的计数(1)

📅  最后修改于: 2023-12-03 15:10:21.843000             🧑  作者: Mango

数组中每个元素右侧的较大元素的计数

介绍

给定一个整数数组,对于其中的每个元素,请统计数组中比它大的元素数目。

例如:

输入: [5,2,6,1]

输出: [2,1,1,0]

解释:

  • 5右侧比5大的数有2个:6和1
  • 2右侧比2大的数有1个:6
  • 6右侧比6大的数有1个:
  • 1右侧比1大的数有0个:
思路

一种简单的思路是,对于每个元素,遍历其右侧的元素,看是否比当前元素大,如果是就计数器+1。

但这种方法的时间复杂度为O(n^2),当数组元素很多时,计算量会非常大。

更好的方法是使用一种数据结构,可以快速的查询出某个元素右侧比它大的元素数目。常用的数据结构有二叉搜索树和树状数组。

这里介绍使用树状数组(Binary Indexed Tree)的方法。

树状数组

树状数组是一种高效的动态维护数组前缀和的数据结构。它支持单点修改、区间查询,时间复杂度均为O(log n)。

我们可以用树状数组来统计一个元素右侧比它大的元素数目。

树状数组需要用一个数组bit[]来保存前缀和,其中bit[i]表示从[ i - 2^k + 1, i]区间的元素和,其中k是i的二进制表示中最低位的1的位数。

可视化的解释可以参考下面这张图:

binary-indexed-tree

代码实现

我们先初始化一个树状数组,将其所有值都初始化为0。

然后,从右向左遍历数组,对于每个元素,我们先查询树状数组中比它大的元素数量,然后再将其加入到树状数组中。

树状数组增加元素的操作可以通过将数组某个位置的值加上1来实现。因为树状数组保存的是区间和,所以在增加元素时,需要将其对应的所有区间的值都加上1。

下面是代码实现:

public int[] countSmaller(int[] nums) {
    int n = nums.length;
    int[] ans = new int[n];

    // 构建树状数组
    int[] bit = new int[n + 1];

    // 从右向左遍历,查询树状数组中比当前元素大的元素数量,
    // 然后将当前元素加入树状数组中,并更新所有对应区间的值。
    for (int i = n - 1; i >= 0; i--) {
        int count = query(bit, nums[i] - 1);
        ans[i] = count;
        update(bit, nums[i]);
    }

    return ans;
}

// 查询树状数组中小于等于x的元素数量
private int query(int[] bit, int x) {
    int sum = 0;
    while (x > 0) {
        sum += bit[x];
        x -= lowbit(x);
    }
    return sum;
}

// 将x加入树状数组中
private void update(int[] bit, int x) {
    while (x < bit.length) {
        bit[x]++;
        x += lowbit(x);
    }
}

// 求x的二进制表示中最低位的1的位数
private int lowbit(int x) {
    return x & (-x);
}
总结

本文介绍了一种时间复杂度为O(n log n)的算法,用于统计一个数组中每个元素右侧比它大的元素数量。

树状数组是一种高效的数据结构,可以用于动态维护数组前缀和。如果您还不熟悉树状数组的使用,请务必了解一下。

这篇文章的主要思路是,从右往左遍历数组,使用树状数组来统计每个元素右侧比它大的元素数量。该方法时间复杂度为O(n log n),空间复杂度为O(n)。