Hard-题目11:315. Count of Smaller Numbers After Self

题目原文:
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()方法.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值