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

本文介绍了一种使用计数法和树状数组解决右侧更小元素数量问题的方法。通过离散化处理原数组,将其元素映射到1至数组长度范围内,再利用树状数组进行高效查询和更新,实现对右侧更小元素数量的快速计算。

使用计数法,维护一个cnt计数数组,从右往左遍历原数组

对于nums中的每个元素i,出现一次,则++cnt[i]
由于要求右边比其小的元素个数,所以即求cnt[i - 1] + cnt[i - 2] + …
这两个问题刚好对应 307题 的 update 和 sumRange操作,可以用树状数组解决(代码中不需要存cnt数组)

与307题不同的是,此题需要将nums中的元素作为树状数组的下标,由于我们不知道nums中的元素情况
比如是否有负数,负数不能作为下标,比如若有一个元素很大很大,则我们的树状数组要开的很大,会造成很多不必要的浪费

所以需要对原数组离散化,将原数组中的元素与1到num.size()一一对应起来,而不改变元素间的大小关系,不会影响最终的结果。
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

struct node
{
    int pos; // 存放原始序号
    int val; // 存放原始值
};

class Solution {
    vector<int> tree; // 树状数组
public:
    vector<int> countSmaller(vector<int>& nums) {
        // 离散化部分
        vector<node> temp;
        for(int i = 0; i < nums.size(); ++i)
            temp.push_back({i, nums[i]});

        // 按val从小到大排序
        sort(temp.begin(), temp.end(), [&](const node a, const node b){
            return a.val < b.val;
        });

        if(nums.size()) nums[temp[0].pos] = 1; // 最小的对应为1
        for(int i = 1; i < nums.size(); ++i)
        {
            // 与前一个元素值相等,则映射的值也相等
            if(temp[i].val == temp[i - 1].val) nums[temp[i].pos] = nums[temp[i - 1].pos];
            else nums[temp[i].pos] = i + 1;
        }

        // 下面是树状数组部分
        tree.resize(nums.size() + 1); // 树状数组下标为1 - n
        fill(tree.begin(), tree.end(), 0);
        
        vector<int> ans;
        // 由于求的是右侧小于当前元素的个数,所以从后往前遍历
        for(int i = nums.size() - 1; i >= 0; --i) 
        {
            update(nums[i]); // nums[i]出现的次数+1
            ans.push_back(sumRange(nums[i] - 1)); // 计算的是小于所以要-1
        }

        reverse(ans.begin(), ans.end()); // 由于从后往前求的,所以要反过来
        return ans;
    }

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

    void update(int i) // 将i位置元素出现的次数+1,向后更新
    {
        for(int j = i; j < tree.size(); j += lowbit(j))
            ++tree[j];
    }

    int sumRange(int i) // 计算i位置(包括i位置)之前的元素和,向前更新
    {
        int sum  = 0;
        for(int j = i; j >= 1; j -= lowbit(j))
            sum += tree[j];
        return sum;
    }
};
为解决LeetCode 315题,即计算整数数组`nums`中每个元素右侧小于元素个数,并将结果存放在新数组`counts`中,可采用以下几种算法: ### 蛮力法 通过两层循环,对于数组中的每个元素,遍历其右侧的所有元素,统计小于元素个数。 ```python def countSmaller(nums): n = len(nums) counts = [0] * n for i in range(n): for j in range(i + 1, n): if nums[j] < nums[i]: counts[i] += 1 return counts ``` 此方法的时间复杂度为$O(n^2)$,其中$n$是数组的长度。该方法的优点是实现简单,缺点是对于大规模数据效率较低 [^4]。 ### 归并排序法 在归并排序的过程中,记录每个元素的原始索引和右侧小于它的元素个数。当合并两个有序子数组时,若从右侧子数组取元素,说明左侧子数组当前元素右侧小于它的元素,更新其计数 [^1]。 ```python def countSmaller(nums): def merge_sort(enum): half = len(enum) // 2 if half: left, right = merge_sort(enum[:half]), merge_sort(enum[half:]) for i in range(len(enum))[::-1]: if not right or left and left[-1][1] > right[-1][1]: smaller[left[-1][0]] += len(right) enum[i] = left.pop() else: enum[i] = right.pop() return enum smaller = [0] * len(nums) merge_sort(list(enumerate(nums))) return smaller ``` 此方法的时间复杂度为$O(n log n)$,空间复杂度为$O(n)$。优点是时间复杂度较低,缺点是实现相对复杂。 ### 二叉搜索树法 构建二叉搜索树,插入元素时记录每个节点右子树的节点个数,即右侧小于元素个数 [^1]。 ```python class TreeNode: def __init__(self, val=0): self.val = val self.left = None self.right = None self.count = 1 self.left_count = 0 def countSmaller(nums): def insert(root, num): if root.val == num: root.count += 1 return root.left_count elif root.val > num: root.left_count += 1 if not root.left: root.left = TreeNode(num) return 0 return insert(root.left, num) else: if not root.right: root.right = TreeNode(num) return root.left_count + root.count return root.left_count + root.count + insert(root.right, num) if not nums: return [] n = len(nums) counts = [0] * n root = TreeNode(nums[-1]) for i in range(n - 2, -1, -1): counts[i] = insert(root, nums[i]) return counts ``` 此方法的平均时间复杂度为$O(n log n)$,但在最坏情况下(数组有序)时间复杂度为$O(n^2)$。优点是可以动态插入元素,缺点是最坏情况下性能较差。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值