对于整数数组类的算法的终极解决方案

首先来思考一个问题,现在有一个数组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;
        }
    }
}
时间: 2024-10-13 16:56:56

对于整数数组类的算法的终极解决方案的相关文章

在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边。

//在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边. // 例如: 当输入a = {8,4,1,6,7,4,9,6,4}, // a = {1,7,9,8,4,6,4,6,4}为一种满足条件的排序结果 using System; namespace SuanFa { class Program { //在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边. static void Main(string[] args) { int[]

算法题:找出整数数组中两个只出现一次的数字

问题:一个整数数组里除了两个数字之外,其他的数字都出现了两次.请写程序找出这两个只出现一次的数字.要求时间复杂度为O(n),空间复杂度为O(1). 分析:这是一个很新颖的关于位运算的题目. 首先考虑这个问题的一个简单版本:一个整数数组里除了一个数字之外,其他的数字都出现两次,请写程序找出这个只出现一次的数字. 这个问题的突破口在哪?题目中数组的性质是只有一个整数出现一次,其他的都出现两次.这样的话就使我们想到了异或运算的性质:任何一个数字异或它自己都等于0.也就是说如果从头到尾依次异或数组中的每

c语言经典算法——查找一个整数数组中第二大数

题目: 实现一个函数,查找一个整数数组中第二大数. 算法思想: 设置两个变量max1和max2,用来保存最大数和第二大数,然后将数组剩余的数依次与这两个数比较,如果这个数a比max1大,则先将max1赋给max2,使原先最大的数成为第二大的数,再将这个数a赋给max1,如果这个数a比max1小但比max2大,则将这个数a赋值给max2,依次类推,直到数组中的数都比较完. c语言代码: 1 #include<stdio.h> 2 #include<stdlib.h> 3 #defin

常见的五类排序算法图解和实现(多关键字排序:基数排序以及各个排序算法的总结)

基数排序思想 完全不同于以前的排序算法,可以说,基数排序也叫做多关键字排序,基数排序是一种借助“多关键字排序”的思想来实现“单关键字排序”的内部排序算法. 两种方式: 1.最高位优先,先按照最高位排成若干子序列,再对子序列按照次高位排序 2.最低位优先:不必分子序列,每次排序全体元素都参与,不比较,而是通过分配+收集的方式. 多关键字排序 例:将下表所示的学生成绩单按数学成绩的等级由高到低排序,数学成绩相同的学生再按英语成绩的高低等级排序.        第一个关键字是数学成绩,第二个关键字是英

java中的数组类与集合类详解及原理介绍

一.类结构概述 当需要存储大量数据对象时,需要用到数组类或者集合类.java中的类结构如下(红色为接口,蓝色为类): Iterator接口:是对collection进行迭代的迭代器,它允许调用者利用定义良好的语义在迭代期间从迭代器所指向的collection移除元素. Collection接口:Collection表示一组对象,最小存储数据颗粒是单一的 List接口:是数组形式,允许数据重复:是有序的 collection(也称为序列),此接口的用户可以对列表中每个元素的插入位置进行精确地控制,

软件工程课程作业(四)--返回一个整数数组中最大子数组的和

伙伴链接:http://www.cnblogs.com/haoying1994/ 一.设计思想 本实验要求输入一个正负数混合的整型数组,长度不限,在此数组的所有子数组中找到和最大的数组,并求出相应数组的和,且时间复杂度为O(n).我们在课堂上共同讨论了多种解决方案,这些将在下面可能的解决方案中展示,在听了同学的思路和老师的讲解之后, 我们最终选取了老师课堂上描述的比较简便的思路.如下: 在输入数组的环节,采用for无限循环加if判断截止,直到触发回车键为止,将数组记录到Array中,数组长度记录

设计数组类扩展数组的功能

建立专门的数组类处理有关数组的操作 数组是几乎所支持的组织数据的方法.C和C++对数组类型提供了内置支持,使我们利用数组实现软件中需要的各种实用的功能.但是,这种支持仅限于用来读写单个元素的机制.C++不支持数组的抽象abstraction,也不支持对整个数组的操作.例如:把一个数组赋值给另外一个数组,对两个数组进行相等比较或者想知道数组的大小size,等等.对C++而言,数组是从C语言中继承来的,它反映了数据与对其进行操作的算法的分离,有浓厚的过程化程序设计的特征.数组并不是C++语言的一等公

数组集合删除算法

数组集合删除算法: 删除: /** * 更多资料欢迎浏览凯哥学堂官网:http://kaige123.com * @author 小沫 */ public void remove(int index){ //objs的长度如果小于0或对象值小于等于0那么抛出数组越界异常 if(objs.length<0||this.index.0){ throw new IndexOutOfBoundsException(); } if(this.index-1==index){ //当前对象的是所占长度-1等

数组集合添加算法

集合是无限存储的容器: 数组集合采用的算法是一开始先开辟好有限的空间进行存储放进来的数据. 等需要再次存放数据的时候,再去开辟一块比原来的空间多的容量之前,老的数据导入进新开辟的空间,然后再把新进来的数据放进空间里,依次这样进行开辟导入就形成了无限的容器.这就是数组集合的算法.  在java源码里面,采用的导入方式是直接调用本地系统语言来直接导入数据,这样提高了效率,一万毫秒才能完成的事情也许四千毫秒就执行完毕. 取消for循环导入使用System.arraycopy如下代码: /** * 更多