题目原文:
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].
Example:
Given nums = [5, 2, 6, 1]
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
Return the array [2, 1, 1, 0].
题目大意:
给出一个数组,计算每个元素右边有几个比它小的元素,组成一个新的数组返回。
题目分析:
这里需要用到一种高级数据结构叫做“树状数组”,又叫Binary Indexed Tree或Fenwick tree,其特点是查询和修改节点都是O(logn)的,用于快速求数组的前n项和。
构建方法:
设原数组是a[1…n],按如图方式构造数组c:(from 百度百科)
C[1]=a[1],c[2]=a[1]+a[2],….c[8]=a[1]+…+a[8],
树状数组有两个api,这两个api的时间复杂度都是O(logn)的,n为数组长度,具体实现略,有兴趣可以自行百度。
void add(int* tree,int k,int num) // 在a[k]上增加num,更新树状数组tree
int get(int* tree,int k) // 求a[1]+…+a[k]的和
再回到这道题,先求最小值和最大值,记为min和max,然后平移区间使得min=1,(若min不为1,则整个数组都减去min和1的差值)然后基于数组num(其中最小值修正为1,对应最大值为max-min+1) 构建树状数组tree[1…max-min+1],其中对应的a数组的a[i]代表num数组中i出现的次数。
接下来逆向扫描num数组,每扫到一个元素num[i],调用get函数求树状数组中a[1]到a[num[i]-1]的和,这个和值就是num数组中i右边比num[i]小的个数!!(这里最难理解,稍后说明),再调用add函数把num[i]加到a中。
时间复杂度:扫描一遍数组O(n),每个元素调用了两个O(logk)复杂度的api,其中k为树状数组长度,总复杂度为O(nlog(max-min)).
上面说了这么多有点抽象,下面举个栗子~~(看明白的童鞋或者嫌我墨迹的童鞋略过吧)
输入数组:[5,6,-1,1] (答案为:[2,2,0,0])
首先修正数组使得最小值为1(否则会出现数组越界):
nums[]=[6,8,1,3],min=1,max=8
然后构建tree[1..8](因为tree的值很难写,就写图中下方的a数组)
逆向扫描数组:此时a数组初始化为全0
nums[i]=3,调用get函数求a[1]+a[2]为0(即1,2这两个数还没出现过),调用add函数使得a[3]++,
此时a数组:[0,0,1,0,0,0,0,0]
Nums[i]=1,调用get函数返回0(因为1左边没有数据了),调用add函数使得a[1]++.
此时a数组:[1,0,1,0,0,0,0,0]
Nums[i]=8,调用get函数求a[1]+…+a[7]=2,调用add函数使得a[8]++
此时a数组:[1,0,1,0,0,0,0,1]
Nums[i]=6,调用get函数求a[1]+…+a[5]=2,调用add函数使得a[6]++.
此时a数组:[1,0,1,0,0,1,0,1]
从下往上读取get的返回值,得到数组[2,2,0,0]即为答案。
源码:(language:java)
public class Solution {
public List<Integer> countSmaller(int[] nums) {
LinkedList<Integer> res = new LinkedList<Integer>();
if (nums == null || nums.length == 0) {
return res;
}
// find min value and minus min by each elements, plus 1 to avoid 0 element
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i = 0; i < nums.length; i++) {
min = (nums[i] < min) ? nums[i]:min;
}
int[] nums2 = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
nums2[i] = nums[i] - min + 1;
max = Math.max(nums2[i],max);
}
int[] tree = new int[max+1];
for (int i = nums2.length-1; i >= 0; i--) {
res.addFirst(get(nums2[i]-1,tree));
update(nums2[i],tree);
}
return res;
}
private int get(int i, int[] tree) {
int num = 0;
while (i > 0) {
num +=tree[i];
i -= i&(-i);
}
return num;
}
private void update(int i, int[] tree) {
while (i < tree.length) {
tree[i] ++;
i += i & (-i);
}
}
}
成绩:
8ms,beats 97.33%,众数11ms,8.79%
Cmershen的碎碎念:
返回类型是List,因为要从下往上读get函数的返回值,故使用链表的实现类LinkedList,因为提供了addFirst()方法.