首先来思考一个问题,现在有一个数组A = [1,2,3,4,5,4,3,2,1,2,3,4,5,4,3,2,1],数组内有一些元素有重复数据,现在要求你给出对于数组中的每一个元素,在右(左)侧有多少元素和它相等(不包括本身),有多少元素大于它,有多少元素的两倍小于(大于)它,3倍,平方.... 甚至更加一般的,有多少元素 (A[i],A[j])(i<j<A.length) 满足Fun(A[i],A[j])?
最笨的一种方法:
int res = 0; for(int i=0;i<A.length;i++){ for(int j=i+1;j<A.length;j++){ if(Fun(A[i],A[j]) == true) res += 1; } } return res;
这种方法的时间复杂度是O(n*n/2),显然是难以让人满意的。
在算法领域,要想降低算法复杂度,就要提高空间复杂度,即所谓的拿空间换时间。如果我们想用空间换时间该怎么换呢?
我们把数组从后向前遍历,每遍历一个元素就把它放到一个仓库里,然后每次求这个元素在仓库里有几个符合条件的值时,直接去仓库里找。只要在仓库里查找和添加、删除足够快(在O(C)时间内完成),我们的时间复杂度就会降低到O(C*n),这显然是可以接受的。
那么有没有这么一种仓库(数据结构),可以实现常数时间内查找、添加、删除元素呢?答案是有的,就是字典树。
我们知道,每一个int都有32位组成。而每一位只有可能是0或者1.这样,只要创建一个深度为32的二叉树就可以描述所有int了。对于每个int的插入、删除、查找的时间都是是32,可见字典树是可以满足我们的需求的。
将问题发散开去,字典树适用于创建那种集合整体可以被[有限个维度]描述的"仓库",而且对单个元素的增加、修改、查找的时间复杂度就是[维度]。
例如英文文章的词频统计,记录每个单词出现的频率,完全可以用字典树来实现。描述所有单词的一个维度就是单词的内容,即一串英文字符,他们的长度虽然可能各不相同,但是总体上是有限的,即[1,100]。而英文字符也是有限集合,通过ASIIC码可以区分他们。
要想用好字典树,一个很关键的因素就是能不能找到一组有限维度来描述整个集合。
然后贴上一段代码:
/****** * 利用字典树保存数组中所有的数*2的值,之后从右向左遍历数组,首先将这个数*2的值从树中删除,然后查找树中比 * 这个数小的元素的个数。 * 最坏时间复杂度: O(96 * n) * 插入、删除、查找还利用了尾递归来加快速度 **/ public class Solution { public int reversePairs(int[] nums) { int res = 0,lenn = nums.length;//,halfMax = (Integer.MAX_VALUE)/2+10; if(lenn<2) return 0; Node head = new Node(); long temp = nums[lenn-1]; //if(nums[lenn-1] < halfMax) addNum(temp *2,head,63); for(int i=lenn-2;i>-1;i--){ temp = nums[i]; res += getRes(temp,head,63,0); addNum(temp *2,head,63); } return res; } private void delNum(long tar,Node he,int bit){ // 删除树中这个元素,并且更新相关节点的值 if(bit<0)return ; if((tar & (((long)1)<<bit)) == 0){ if(he.zero.num > 0)he.zero.num -= 1; //else return ; he = he.zero; }else{ if(he.one.num > 0)he.one.num -= 1; //else return ; he = he.one; } delNum(tar,he,bit-1); } private void addNum(long tar,Node he,int bit){ // 向树中增加这个元素,并且更新相关节点的值 if(bit<0)return; if((tar & (((long)1)<<bit)) == 0){ if(he.zero == null) he.zero = new Node(); else he.zero.num += 1; he = he.zero; }else{ if(he.one == null) he.one = new Node(); else he.one.num += 1; he = he.one; } addNum(tar,he,bit-1); } private int getRes(long tar,Node he,int bit,int res){ // 符号位特殊判断 if(tar>=0){ if(he.one != null)res += he.one.num; he = he.zero; }else{ he = he.one; } return getSmallerNum(tar,he,bit-1,res); } private int getSmallerNum(long tar,Node he,int bit,int res){ // 获取比tar小的所有数的个数,据说是尾递归 if(bit<0 || he == null) return res; if((tar & (((long)1)<<bit)) == 0){ he = he.zero; }else{ if(he.zero != null)res += he.zero.num; he = he.one; } return getSmallerNum(tar,he,bit-1,res); } private static class Node{ public int num; public Node zero,one; public Node(){ num = 1; zero = null; one = null; } } }