【困难】力扣算法题解析LeetCode315:计算右侧小于当前元素的个数

关注文末推广名片,即可免费获得本题测试源码

题目来源:LeetCode315:计算右侧小于当前元素的个数

问题抽象: 给定一个整数数组 nums,要求计算并返回一个新数组 counts,其中 counts[i] 表示在原数组中位于索引 i 右侧且 小于 nums[i] 的元素个数,满足以下核心需求:

  1. 统计规则定义

    • 右侧范围:仅统计索引 > i 的位置(严格在 nums[i] 右侧);
    • 严格小于:仅计算值 严格小于 nums[i] 的元素(等于不算);
    • 独立计算:每个索引 icounts[i] 独立统计(结果数组长度与 nums 相同)。
  2. 输入输出

    • 输入:整数数组 nums(长度 1 ≤ n ≤ 10^5,元素值 -10^4 ≤ nums[i] ≤ 10^4);
    • 输出:整数数组 countscounts[i] 为右侧小于 nums[i] 的元素个数)。
  3. 计算约束

    • 时间复杂度 O(n log n)(需优于暴力法 O(n^2));
    • 空间复杂度 O(n)(存储结果及中间数据结构);
    • 需高效处理 大规模输入n=10^5)。
  4. 边界处理

    • 降序数组:如 [5,4,3] → 输出 [2,1,0](右侧所有元素更小);
    • 升序数组:如 [1,2,3] → 输出 [0,0,0](右侧无更小元素);
    • 重复元素:如 [2,2,1]counts[0]=1(右侧 1<2),counts[1]=1(右侧 1<2),counts[2]=0
    • 负数和零:如 [-1,0,-2] → 输出 [1,1,0]

输入:整数数组 nums
输出:整数数组 countscounts[i] 表示右侧小于 nums[i] 的元素数)。


解题思路

本题要求在数组的每个位置统计其右侧小于当前元素的个数。由于数组长度最大可达 (10^5),暴力解法(时间复杂度 (O(n^2)))会超时,需要采用更高效的算法。以下是解题思路:

  1. 归并排序与索引数组

    • 使用归并排序的思想,在排序过程中统计每个元素右侧小于它的元素数量。
    • 创建一个索引数组 index,初始时 index[i] = i,表示每个元素的原始位置。
    • 归并排序过程中,对索引数组进行排序而非原数组,以保留元素的原始位置信息。
  2. 合并过程中的统计

    • 在合并两个有序子数组(左子数组 [left, mid] 和右子数组 [mid+1, right])时:
      • 左子数组和右子数组已分别有序(升序)。
      • 使用双指针 i(左子数组)和 j(右子数组)进行合并:
        • nums[index[i]] <= nums[index[j]]:将左子数组元素放入临时数组,并更新结果:res[index[i]] += j - (mid + 1)(即右子数组中已合并的元素个数)。
        • nums[index[i]] > nums[index[j]]:将右子数组元素放入临时数组。
    • 处理剩余元素:
      • 若左子数组有剩余,每个剩余元素对应的结果需加上整个右子数组的长度(即 j - mid - 1)。
      • 若右子数组有剩余,直接放入即可(无需更新结果)。
  3. 结果累计

    • 每个元素的计数由两部分组成:
      • 递归过程中子数组内部的计数(已处理)。
      • 合并时左子数组元素相对于右子数组的计数(跨子数组)。
    • 最终结果数组 res 记录了每个原始位置元素的右侧小于它的数量。

复杂度分析:时间复杂度:O(n log n),归并排序的复杂度。空间复杂度:O(n),用于索引数组和临时数组。


代码实现(Java版)🔥点击下载源码

class Solution {
    public List<Integer> countSmaller(int[] nums) {
        int n = nums.length;
        // 初始化索引数组和结果数组
        int[] index = new int[n];
        int[] res = new int[n];
        for (int i = 0; i < n; i++) {
            index[i] = i;
        }
        // 临时数组用于归并排序
        int[] tempIndex = new int[n];
        // 归并排序
        mergeSort(nums, index, res, 0, n - 1, tempIndex);
        // 将结果数组转为List
        List<Integer> resultList = new ArrayList<>();
        for (int count : res) {
            resultList.add(count);
        }
        return resultList;
    }

    private void mergeSort(int[] nums, int[] index, int[] res, int left, int right, int[] tempIndex) {
        if (left >= right) {
            return;
        }
        int mid = left + (right - left) / 2;
        // 递归排序左子数组和右子数组
        mergeSort(nums, index, res, left, mid, tempIndex);
        mergeSort(nums, index, res, mid + 1, right, tempIndex);
        // 合并两个有序子数组
        merge(nums, index, res, left, mid, right, tempIndex);
    }

    private void merge(int[] nums, int[] index, int[] res, int left, int mid, int right, int[] tempIndex) {
        int i = left;
        int j = mid + 1;
        int k = left;
        // 合并过程
        while (i <= mid && j <= right) {
            if (nums[index[i]] <= nums[index[j]]) {
                // 左子数组元素小于等于右子数组元素:更新结果并放入临时数组
                res[index[i]] += j - (mid + 1);
                tempIndex[k++] = index[i++];
            } else {
                // 右子数组元素较小:直接放入临时数组
                tempIndex[k++] = index[j++];
            }
        }
        // 处理左子数组剩余元素
        while (i <= mid) {
            res[index[i]] += j - (mid + 1);
            tempIndex[k++] = index[i++];
        }
        // 处理右子数组剩余元素
        while (j <= right) {
            tempIndex[k++] = index[j++];
        }
        // 将临时数组中的排序结果复制回原索引数组
        System.arraycopy(tempIndex, left, index, left, right - left + 1);
    }
}

代码说明

  1. 初始化

    • index 数组记录元素的原始位置,res 数组记录结果。
    • tempIndex 数组用于归并排序过程中的临时存储。
  2. 归并排序

    • 递归分割数组至最小单位(单元素),然后合并。
    • 在合并过程中,根据左右子数组的元素大小关系更新结果数组 res
  3. 合并逻辑

    • 左子数组元素较小或相等时:更新 res[index[i]],增加值为右子数组已合并元素的数量(j - (mid + 1))。
    • 左子数组有剩余时:每个剩余元素对应的结果增加整个右子数组的长度(j - (mid + 1))。
    • 右子数组元素较小时:直接合并,无需更新结果(因为右子数组元素在原始数组中位于右侧,但统计的是左侧元素对右侧的贡献)。
  4. 结果转换

    • res 数组转为 List<Integer> 返回。

提交详情(执行用时、内存消耗)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值