记录 1 道算法题
计算右侧小于左侧当前元素的个数
要求:提供一个整数数组,返回数组中每一个元素的在它右侧并且比它小的数的个数组成的数组。
比如:[5,2,6,1] 输出:[2,1,1,0]
我们可以通过归并排序进行解题,因为进行分治的时候从两个两个进行比较,一直返回升序数组,分为了左边和右边,只需要对左右进行比较就好了,不需要对同一边进行比较,因为同一边已经比较完毕,得到递归返回的结果。
在进行左右对比的时候,由于升序的关系,只要比一个数大,比它后面的数都大,比它前面的数都小。
左右是固定的,所以当右边比左边小的时候则是我们需要的右侧小于当前元素的数。而个数则是右边的指针左侧的元素的个数。
比如 [2,3,4] 和 [5,6,7]
进行归并的时候
[5,6,7] [2,3,4]
i j
[2]
比 5 小的右侧元素是 j 左侧的数 0 个
[5,6,7] [2,3,4]
i j
[2,3]
比 5 小的右侧元素是 j 左侧的数 1 个是 2
[5,6,7] [2,3,4]
i j
[2,3,4]
比 5 小的右侧元素是 j 左侧的数 2 个是 2,3
[5,6,7] [2,3,4]
i j
[2,3,4,5,6]
比 6 小的右侧元素是 j 左侧的数 3 个是 2,3,4
[5,6,7] [2,3,4]
i j
[2,3,4,5,6,7]
比 7 小的右侧元素是 j 左侧的数 3 个是 2,3,4
这一层递归结束后返回
[2,3,4,5,6,7] 和 [?,?,?,?,?,?] 进行归并,左边和右边比较
需要注意的是由于进行了排序,所以下标会发生改变,需要用一个记录下标的数组,在排序时同时调换记录下标的数组的位置。这样可以通过读这个数组的值得到计数数组的对应位置。
原数组[5,2,6,1]
下标 [0,1,2,3]
计数 [0,0,0,0]
排序后
[2,5,1,6]
[1,0,3,2]
[1,0,1,0]
这样可以对正确的地方的计数进行 ++
完整代码如下:
function countSmaller(nums) {
// 保存下标
const map = Array.from(nums, (_, i) => i)
const result = new Array(nums.length).fill(0)
merge(nums, 0, nums.length - 1, map, result)
return result
}
function merge(arr, start, end, map, result) {
// 只有一个的时候
if (start >= end) return
const mid = Math.floor((start + end) / 2)
merge(arr, start, mid, map, result)
merge(arr, mid + 1, end, map, result)
// 存储归并的结果
const res = []
// 存储需要移动的下标
const index = []
let a = start
let b = mid + 1
// 需要两边都遍历完才结束循环
while(a <= mid || b <= end) {
// 左边遍历完了,右边加进去
if (a > mid) {
res.push(arr[b])
index.push(map[b])
b++
} else if (b > end) {
// 当右侧数组已经遍历完,左边数组的任何一个数都比右边的大
result[map[a]] += (b - mid - 1)
res.push(arr[a])
index.push(map[a])
a++
} else if (arr[a] <= arr[b]) {
res.push(arr[a])
// 左边比右边小,正在加入左边的数,右边指针往左的数都是比左边指针的数小
result[map[a]] += (b - mid - 1)
index.push(map[a])
a++
} else {
res.push(arr[b])
index.push(map[b])
b++
}
}
// 将存储的移动结果进行更新
for(let i = 0; i < res.length; i++) {
// 排序
arr[i + start] = res[i]
// 下标也跟着移动
map[i + start] = index[i]
}
}