题目来源:LeetCode315:计算右侧小于当前元素的个数
问题抽象: 给定一个整数数组 nums,要求计算并返回一个新数组 counts,其中 counts[i] 表示在原数组中位于索引 i 右侧且 小于 nums[i] 的元素个数,满足以下核心需求:
-
统计规则定义:
- 右侧范围:仅统计索引
> i的位置(严格在nums[i]右侧); - 严格小于:仅计算值 严格小于
nums[i]的元素(等于不算); - 独立计算:每个索引
i的counts[i]独立统计(结果数组长度与nums相同)。
- 右侧范围:仅统计索引
-
输入输出:
- 输入:整数数组
nums(长度1 ≤ n ≤ 10^5,元素值-10^4 ≤ nums[i] ≤ 10^4); - 输出:整数数组
counts(counts[i]为右侧小于nums[i]的元素个数)。
- 输入:整数数组
-
计算约束:
- 时间复杂度 O(n log n)(需优于暴力法
O(n^2)); - 空间复杂度 O(n)(存储结果及中间数据结构);
- 需高效处理 大规模输入(
n=10^5)。
- 时间复杂度 O(n log n)(需优于暴力法
-
边界处理:
- 降序数组:如
[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
输出:整数数组 counts(counts[i] 表示右侧小于 nums[i] 的元素数)。
解题思路
本题要求在数组的每个位置统计其右侧小于当前元素的个数。由于数组长度最大可达 (10^5),暴力解法(时间复杂度 (O(n^2)))会超时,需要采用更高效的算法。以下是解题思路:
-
归并排序与索引数组:
- 使用归并排序的思想,在排序过程中统计每个元素右侧小于它的元素数量。
- 创建一个索引数组
index,初始时index[i] = i,表示每个元素的原始位置。 - 归并排序过程中,对索引数组进行排序而非原数组,以保留元素的原始位置信息。
-
合并过程中的统计:
- 在合并两个有序子数组(左子数组
[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)。 - 若右子数组有剩余,直接放入即可(无需更新结果)。
- 若左子数组有剩余,每个剩余元素对应的结果需加上整个右子数组的长度(即
- 在合并两个有序子数组(左子数组
-
结果累计:
- 每个元素的计数由两部分组成:
- 递归过程中子数组内部的计数(已处理)。
- 合并时左子数组元素相对于右子数组的计数(跨子数组)。
- 最终结果数组
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);
}
}
代码说明
-
初始化:
index数组记录元素的原始位置,res数组记录结果。tempIndex数组用于归并排序过程中的临时存储。
-
归并排序:
- 递归分割数组至最小单位(单元素),然后合并。
- 在合并过程中,根据左右子数组的元素大小关系更新结果数组
res。
-
合并逻辑:
- 左子数组元素较小或相等时:更新
res[index[i]],增加值为右子数组已合并元素的数量(j - (mid + 1))。 - 左子数组有剩余时:每个剩余元素对应的结果增加整个右子数组的长度(
j - (mid + 1))。 - 右子数组元素较小时:直接合并,无需更新结果(因为右子数组元素在原始数组中位于右侧,但统计的是左侧元素对右侧的贡献)。
- 左子数组元素较小或相等时:更新
-
结果转换:
- 将
res数组转为List<Integer>返回。
- 将
提交详情(执行用时、内存消耗)

473

被折叠的 条评论
为什么被折叠?



