并归排序的应用 I
1. 题目列表
题目列表:
序号 | 题目 | 难度 |
---|---|---|
1 | 315. 计算右侧小于当前元素的个数 | 困难 |
2. 应用
2.1. Leetcode 315. 计算右侧小于当前元素的个数
2.1.1. 题目
给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例 1:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
- 5 的右侧有 2 个更小的元素 (2 和 1)
- 2 的右侧仅有 1 个更小的元素 (1)
- 6 的右侧有 1 个更小的元素 (1)
- 1 的右侧有 0 个更小的元素
2.1.2. 解题思路
这里,我们需要借助并归排序的思想:在每一次合并两个有序数组的时候,两个子数组都会按照从小到大的顺序合并,那么,每合并一个左侧的子数组中的元素时,它可以通过右侧的子数组中的待合并元素的位置,就是当前两个子数组中小于当前元素的个数。这样,我们只需要完成所有子数组的合并,即可得到每一个元素右侧小于它的元素个数了。
这里,我们以数组 \(nums = [3,1,5,7,2,6,4]\) 的并归排序过程为例,来介绍查找右侧小于当前元素的过程:
在并归排序的在某一个步骤,我们会按照从小到大的顺序依次合并两个子数组:\(temp[left \cdots mid] = [1,3,5]\)、\(temp[mid + 1 \cdots right]=[2,4,6,7]\) :
当我们依次合并两个子数组时,在某一个时刻,应该合并 \(temp[i] = 5\) 的时候,可以看出,在子数组 \(temp[mid + 1 \cdots right]\) 中,比 \(5\) 小的元素,就是索引 \(j\) 到 \(temp[mid + 1 \cdots right]\) 开头的距离,即 \(2\) 个。
也就是说,在对 \(nuns[left \cdots right]\) 合并的过程中,当我们选择一个左侧子数组中的元素 \(temp[i]\) 时,它总是可以通过右侧子数组中的待合并元素 \(temp[j]\) 确定小于当前元素的个数,即每当执行 \(nums[k] = temp[i]\) 时,就可以确定 \(temp[i]\) 这个元素后面比它小的元素个数为 \(j - mid - 1\)。
注意,因为每一次子数组的合并,都会对结果产生贡献,因此,我们需要将所有的结果累加起来。
总结:这里,我们主要利用了并归排序的思路,在每一次合并过程中,都可以通过右侧的子数组,求出小于当前元素的个数。
2.1.3. 算法步骤
这里,我们定义一个数据结构 \(Node\) 用于记录原始数组中的每一个元素的值及其位置索引:
class Node {
int val;
int id;
Node(int val, int id) {
this.val = val;
this.id = id;
}
}
同时,我们需要定义两个变量:
-
数组 \(count\) 用于记录每一个索引位置所对应元素的右侧小于它的元素个数。
-
数组 \(nodes\) 用于对 \(Node\) 做并归排序。
算法步骤:
-
先将原始数组 \(nums\) 中的数值,记录到数组 \(nodes\) 中;
-
对数组 \(nodes\) 使用并归排序;
-
在每次合并两个子数组时,对于每一个左侧子数组中的元素,右侧小于它的元素个数都可以通过右侧子数组中待合并元素的位置得到;
-
递归合并完所有的子数组,即可得到结果。
-
2.1.4. 代码实现
class Solution {
private class Node {
int val;
int id;
Node(int val, int id) {
this.val = val;
this.id = id;
}
}
private Node[] temp;
private int[] count;
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
count = new int[n];
temp = new Node[n];
Node[] nodes = new Node[n];
for (int i = 0; i < n; i++) {
nodes[i] = new Node(nums[i], i);
}
sort(nodes, 0, n - 1);
List<Integer> result = new LinkedList<>();
for (int c : count) {
result.add(c);
}
return result;
}
private void sort(Node[] nodes, int left, int right) {
if (left == right) {
return;
}
int mid = left + (right - left) / 2;
sort(nodes, left, mid);
sort(nodes, mid + 1, right);
merge(nodes, left, mid, right);
}
private void merge(Node[] nodes, int left, int mid, int right) {
for (int i = left; i <= right; i++) {
temp[i] = nodes[i];
}
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
if (temp[i].val > temp[j].val) {
nodes[k] = temp[j++];
} else {
nodes[k] = temp[i++];
count[nodes[k].id] += j - mid - 1; // 合并左侧子树的时候,累加结果
}
k++;
}
while (i <= mid) {
nodes[k] = temp[i++];
count[nodes[k].id] += j - mid - 1; // 合并左侧子树的时候,累加结果
k++;
}
while (j <= right) {
nodes[k++] = temp[j++];
}
}
}
参考: