剑指Offer数据结构:对于数组部分个人的理解

数组是一种最基本的数据结构,它采用一组连续的内存空间按顺序存储对象或基本数据类型。那么我们在Java程序中,声明一个数组会要求先指明其容量大小,那么这个机制就带来了一些问题。

问题1,数组的扩容问题

例如我们创建了一个容量为10的数组,后期需要将其扩容为容量12的数组,这样我们又必须手动去修改它的容量。若只需要修改一两次还好,如果需要修改1000次,10000次呢?
为了解决这个问题,就诞生了Collection集合中的ArrayList,ArrayList是底层基于Object[ ]数组实现的一个动态数组,初始长度为10,其中存在一个扩容机制,关于ArrayList的具体实现可以参考源码,这里就不具体细说了。

问题2,数组的空间浪费问题

例如我们创建了一个容量为10的数组,预期往里面存储10条数据,但是由于某些原因我们只存储了1条数据,那么数组剩余的部分就造成了一定的资源浪费,因此数组的空间效率不是很好,经常会有空闲的区域没有被使用。
对于这个问题,我们不能解决只能优化,毕竟谁也不能预见创建一个数组后是否会需要扩容的情况。于是该优化机制只能通过ArrayList的扩容机制进行完成。具体实现参考ArrayList源码。

需要注意的是:ArrayList每一次进行扩容,都需要进行额外的开销,这对时间性能存在负面影响,因此即便是使用自动扩容的动态数组,我们也需要尽可能的减少改变数组容量大小的操作。

那么总结以下数组的缺点之后,我们再来谈一谈数组的优点。

优点1,查询效率高

因为数组中每个元素都对应着一个独一无二的下标,那么可想而知,数组的查询效率是非常高的。我们可以根据下表在O(1)时间读/写任何元素,因此数组的时间效率是很高的。

根据数组其特点,抛出一个问题

如何用数组实现一个HashMap?

我们都知道HashMap采用key-value的形式进行数据存储,底层采用数组+链表(JDK1.8的红黑树)实现,那么HashMap的查询的特点是什么呢?就是根据key查找对应的value对吧。是不是有点像数组根据下标查找对应的元素。
那么我们就可以通过把数组的下标设置为key,把数组的元素设置为value,这样数组中每个元素都形成一个key-value键值对,也就形成了一个简单的HashMap了。

概念需要通过实践理解,那么抛出问题。

找出数组中重复的数字

在一个长度为n的数组中,所有数字都在0~n-1的范围内,数组中有些数字是重复的,但并不知道几个数字重复了,也不知道重复的数字重复了几次。请找出数组中任意一个重复的数字,例如一个长度为7的数组{2,3,1,0,2,5,3},对应输出2或3。

在解决问题之前我们先理清思路,首先我们需要将数组进行排序(快速排序,时间复杂度O(nlogn)),然后遍历,找出两两重复的数字就好。

public static void duplicatedNum01(int array[]){
        if(array == null|| array.length< 0){
            System.out.println("error");
            return;
        }
        //1 快速排序
        Arrays.sort(array);
        for(int i = 0;i< array.length-1;i++){
            if(array[i] == array [i+1]){
                System.out.println(array[i]);
            }
        }
    }

以上是一个非常简单的实现,我们还可以通过HashMap进行实现:从头到尾扫描这个数组的每个元素,每扫描一个元素,都可以用O(1)的时间复杂度进行判断Hash表中是否已经存在该元素。若没有则添加,若有则输出。这个算法的时间复杂度为O(n)。

public static void duplicatedNum02(int array[]){
        if(array == null|| array.length< 0){
            System.out.println("error");
            return;
        }
        HashMap map = new HashMap();
        for(int i = 0;i<=array.length-1;i++){
            if(map.containsValue(array[i])){
                System.out.println(array[i]);
            }else {
                map.put(i, array[i]);
            }
        }
    }

第三个方法思路很清奇…属性我这种菜鸡想破头都不可能想到的算法…从头到尾依次扫描这个数组,当扫描的下标为 i 时,首先比较这个数字(用m表示)是否和下标 i 相等(充分地利用了数组下表有序的特性),如果是则扫描下一个数字,如果不是则将这个数字与下标为它本身数值 m 的数字比较,若相等则找到一个重复的数字(当排序到最后,前面的元素基本已经有序),若不相等二者交换位置。接下来继续判断数字本身的值是否等于其下标,若相等则扫描下一个数字,若不相等则重复之前的步骤。

太过抽象举个例子吧:{2,3,1,0,4,3},首先下标为0的2 它是否与其下标0相等,不相等则将下标为2的1比较,不相等则交换位置,此时数组为{1,3,2,0,4,3}。还是下标为0的1 它是否与下标0相等,不相等则与下标为1的3比较,也不相等,交换位置,此时数组为{3,1,2,0,4,3},重复步骤…,{0,1,2,3,4,3},这时我们突然发现,前面的数据不知不觉中竟然有序了!此时直接跳到了下标为5的3,它不与下标5相等,与下标为3的3比较,相等,找到重复。

public static void duplicatedNum03(int array[]){
        if(array == null|| array.length< 0){
            System.out.println("error");
            return;
        }
        for(int i = 0;i<=array.length-1;i++){
            while (i != array[i]){
                if(array[i] == array[array[i]]){
                    System.out.println(array[i]);
                    break;
                }
                int temp = array[i];
                array[i] = array[temp];
                array[temp] = temp;
            }
        }
    }

本质也是一个排序算法,尽管有两重循环,但每个数字只需要交换2次,就能够找到属于它的位置,因此总的时间复杂度为O(n),此外,所有操作步骤都在数组中进行,不需要分配额外内存,空间复杂度为O(1)。

毫不夸张地说,第三个算法我是这辈子都想不出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值