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

博客介绍了如何解决计算右侧小于当前元素的个数问题,提供了两种解题方法:归并排序和树状数组。在归并排序中,通过优化统计左路元素大于右路元素的个数。树状数组方法则利用前缀和统计数组中数字的出现次数,需要对数据进行离散化处理以适应大范围数值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

思路

归并排序

在归并排序中统计依次统计左路元素大于右路元素的个数,注意一个优化技巧,由于左右路元素已经排序完毕,在统计左路元素大于右路元素时,统计左路元素下一个时,r指针无需归位,因为左路元素已经按从小到大排序。

代码

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        vector<pair<int, int>> v;
        vector<int> ans(n, 0);
        for(int i = 0; i < n; i++) {
            v.push_back(make_pair(nums[i], i));
        }
        mergesort(v, ans, 0, n - 1);
        return ans;
    }

    void mergesort(vector<pair<int, int>>& v, vector<int>& ans, int left, int right) {
        if(left >= right) {
            return ;
        }
        int mid = left + (right - left) / 2;
        mergesort(v, ans, left, mid);
        mergesort(v, ans, mid + 1, right);
        int r = mid + 1;
        for(int l = left; l <= mid; l++) {
            while(r <= right && v[l].first > v[r].first) {
                r++;
            }
            ans[v[l].second] += r - mid - 1;
        }
        merge(v, left, mid, right);
    }

    void merge(vector<pair<int, int>>& v, int left, int mid, int right) {
        int n = right - left + 1;
        vector<pair<int, int>> tmp(n);
        int l = left, r = mid + 1;
        int start = 0;
        while(l <= mid && r <= right) {
            if(v[l].first > v[r].first) {
                tmp[start++] = v[r++];
            }
            else {
                tmp[start++] = v[l++];
            }
        }

        while(l <= mid) {
            tmp[start++] = v[l++];
        }
        while(r <= right) {
            tmp[start++] = v[r++];
        }

        for(int i = 0; i < n; i++) {
            v[left + i] = tmp[i];
        }       
    }
};

树状数组

树状数组是统计前缀和一个非常有效的数据结构,假定我们从后往前遍历数组,统计数字的出现次数,那么访问到num时,只需要统计0到num-1的出现次数即可。换言之就是统计num-1的前缀次数和。
但是这种解法有一个缺点,数组的数据范围可能很大,不可能根据数组范围开数组,需要将数据进行离散化,也就是将原数据的值域映射到一个连续的整数区间,保证他们的偏序不变。这里使用原数组去重后排序,原数组每个数映射到去重排序后这个数对应位置的下标,我们称这个下标为这个对应数字的id。

代码

class Solution {
private:
    vector<int> a;
    vector<int> c;

    void Init(int len) {
        c.resize(len, 0);
    }

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

    void update(int id) {
        while(id < c.size()) {
            c[id] += 1;
            id += lowbit(id);
        }
    }

    int query(int id) {
        int ret = 0;
        while(id > 0) {
            ret += c[id];
            id -= lowbit(id);
        }
        return ret;
    }

    void Discretization(vector<int>& nums) {
        a.assign(nums.begin(), nums.end());
        sort(a.begin(), a.end());
        a.erase(unique(a.begin(), a.end()), a.end());
    }

    int getid(int num) {
        return lower_bound(a.begin(), a.end(), num) - a.begin() + 1;
    }

public:
    vector<int> countSmaller(vector<int>& nums) {
        int len = nums.size();

        Init(len + 2);
        Discretization(nums);
        vector<int> ans;

        for(int i = len - 1; i >= 0; i--) {
            int id = getid(nums[i]);
            ans.push_back(query(id - 1));
            update(id);
        }

        reverse(ans.begin(), ans.end());
        return ans;
    }
};
### 归并排序计算逆序对数量的方法 归并排序是一种基于分治法的高效排序算法,其核心思想是将数组分为两部分分别处理,再合并这两部分的结果。在合并的过程中可以统计逆序对的数量[^2]。 当使用归并排序来计算逆序对时,在每次从右侧子数组取数放到最终位置时,左侧剩余未处理的部分都可以与当前右侧取出的数字构成一组或多组逆序对[^4]。具体来说: - 假设左半部分为 `left` 和右半部分为 `right`。 - 如果我们在合并过程中发现 `right[j] < left[i]`,那么意味着 `left[i..mid]` 中的所有元素都比 `right[j]` 大,从而形成了多个逆序对。 - 这些逆序对的数量可以通过简单的数学运算得出:`(mid - i + 1)`。 以下是完整的 C 实现代码示例: ```c #include <stdio.h> long long mergeAndCount(int *arr, int *tempArr, int left, int mid, int right) { int i = left; int j = mid + 1; int k = left; long long count = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { // 不形成逆序对的情况 tempArr[k++] = arr[i++]; } else { // 当右边小于左边时,形成逆序对 tempArr[k++] = arr[j++]; count += (mid - i + 1); // 统计逆序对数量 } } while (i <= mid) { // 将剩余的左半部分复制到临时数组 tempArr[k++] = arr[i++]; } while (j <= right) { // 将剩余的右半部分复制到临时数组 tempArr[k++] = arr[j++]; } for (int l = left; l <= right; ++l) { // 把临时数组的内容拷贝回原数组 arr[l] = tempArr[l]; } return count; } long long mergeSortAndCount(int *arr, int *tempArr, int left, int right) { long long inv_count = 0; if (left < right) { int mid = (left + right) / 2; inv_count += mergeSortAndCount(arr, tempArr, left, mid); inv_count += mergeSortAndCount(arr, tempArr, mid + 1, right); inv_count += mergeAndCount(arr, tempArr, left, mid, right); } return inv_count; } long long getInversionCount(int *arr, int n) { int *tempArr = (int *)malloc(sizeof(int) * n); long long result = mergeSortAndCount(arr, tempArr, 0, n - 1); free(tempArr); return result; } // 测试函数 void test() { int arr[] = {7, 5, 6, 4}; int n = sizeof(arr) / sizeof(arr[0]); printf("Number of Inversions are %lld\n", getInversionCount(arr, n)); } int main() { test(); return 0; } ``` #### 解释 上述程序实现了利用归并排序计算逆序对的功能: 1. **mergeAndCount 函数** 负责实际的合并操作以及逆序对的统计工作。 2. **mergeSortAndCount 函数** 是递归调用的核心逻辑,负责分割数组并对每一部分进行逆序对统计。 3. **getInversionCount 函数** 提供了一个接口用于初始化辅助数组,并启动整个流程。 此方法的时间复杂度为 O(n log n),空间复杂度为 O(n)[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值