算法通关村第三关——数组增删改查笔记

一、线性表基础

1、线性表

        所谓线性表就是具有相同特征数据元素的一个有限序列,其中所含元素的个数称为线性表的长度,从不同的角度看,线性表可以有不同的分类,例如:

从语言实现的角度:
        图a为一体式结构,存储表信息的单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体形成一个完整的顺序表对象。这种结构整体性强,易于管理,但是由于数据元素存储区域是表对象的一部分,顺序表创建后,元素存储区就固定了。C和C++都是一体式结构。
        图b为分离式结构,表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。Java和python是分离式结构。
从存储的角度:
        从存储的角度看,可以分为顺序型和链表型。顺序型就是将数据存放在一段固定的区间内,此时访问元素的效率非常高,但是删除和增加元素代价比较大,如果要扩容只能整体搬迁。而在链表型里,元素之间是通过地址依次连接的,因此访问时必须从头开始逐步向后找,因此查找效率低,而删除和增加元素非常方便,并且也不需要考虑扩容的问题。
从访问限制的角度:
        栈和队列又称为访问受限的线性表,插入和删除受到了限制,只能在固定的位置进行。而Hash比较特殊,其内部真正存储数据一般是数组,但是访问是通过映射来实现的,因此大部分材料里并不将Hash归结到线性表中。线性表的知识架构如下:

        线性表的常见操作有初始化、求表长、增删改查等,事实上每种数据结构都至少要有这几种操作,大部分的基础算法题都是基于此扩展的。
从扩容的角度:
        采用分离式结构的顺序表,若将数据区更换为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行了扩充,所有使用这个表的地方都不必修改,只要程序的运行环境(计算机系统)还有空闲存储,这种表结构就不会因为满了而导致操作无法进行。人们把采用这种技术实现的顺序表称为动态顺序表,因为其容量可以在使用中动态变化。
扩充的两种策略:

  • 第一种:每次扩充增加固定数目的存储位置,如每次扩充增加10个元素位置,这种策略可称为线性增长。特点:节省空间,但是扩充操作频繁,操作次数多。
  • 第二种:每次扩充容量加倍,如每次扩充增加一倍存储空间。特点:减少了扩充操作的执行次数,但可能会浪费空间资源。以空间换时间,推荐的方式。

        具体到每种结构语言中的结构,实现方式千差万别,其中Java基本是扩容时加倍的方式,而在python的官方实现中,list实现采用了如下的策略:在建立空表(或很小的小)时,系统分配一块能容纳8个元素的存储区;在执行插入操作(insert或append)时,如果元素存储区满就换一块4倍大的存储区,但如果此时的表已经很大了(目前的阈值为50000),则改变策略,采用加一倍的方法。引入这种改变策略的方式是为了避免出现过多空闲的存储位置。

2、数组的概念

        数组是线性表最基本的结构,特点是元素是一个紧密在一起的序列,相互之间不需要记录彼此的关系就能访问,例如月份、星座等。
        数组用索引的数字来标识每项数据在数组中的位置,且在大多数编程语言中,索引是从0算起的。我们可以根据数组中的索引快速访问数组中的元素。

        数组有两个需要注意的点,一个是从0开始记录,也就是第一个存元素的位置是a[0],最后一个是a[length-1]。其次,数组中的元素在内存中的连续存储的,且每个元素占用相同大小的内存。

        另外需要注意的是数组空间不一定是满的,100的空间可能只用了10个位置,所以要注意数据个数的变量size和数组长度length可能不一样,解题时必须注意。

3、数组存储元素的特征

(1)数组创建,不同语言处理会不一样,在C语言里每个位置都是一个随机数;而在java里,默认会初始化为0;而python更为灵活可以直接指定是什么,例如a=[1,2,3,4],就是数组里有四个元素,而a=[0 for i inrange(10)]这样定义的数组就是[0,0,0,0,0,0,0,0,0,0]。
(2)数组初始化是可以初始一部分位置的,你可以将前面五个位置依次,后面的空着,因此数组内容为{1,2,3,4,5,0,0,0,0,0}。数组的初始化的本质就是覆盖已有的值,用你需要的值覆盖原来的0,因为数组本来是{0,0,0,0,0,0,0,0,0,0},这里只不过被你替换成了{1,2,3,4,5,0,0,0,0,0}。如果此你想知道有效元素的个数,就必须再使用一个额外的变量,例如size来标记。
(3)注意,初始化必须是从前向后的连续空间初始化,不可以出现空缺的情况,这是违背数组的原则的。你正在进行某种运算期间可以给部分位置赋值,而一旦稳定了,就不可以再出现空位置的情况。
(4)如果需要的数据就是在中间某一时段的情况,例如{0,0,3,4,5,6,7,0,0,0},此时如果想拿到3到7的元素,你需要使用两个变量,例如left=2,right=6来表示区间[left,right]是有效的。
(5)元素删除的时候,例如原始数组为{1,2,3,4,5,6,7,8,0,0},删除4之后,根据数组的移动原则,从5开始向前移动,变成{1,2,3,5,6,7,8,?,0,0},原来8的位置,仍然是8,也就是删除4之后的结构为{1,2,3,5,6,7,8,8,0,0},此时表示元素数量的size会减1变成7,原来的8的位置仍然是8,因为我们是通过size来标记元素数量的,所以最后一个8不会被访问到。
(6)这个8,不用优化,优化了也没用。

二、数组基本操作

在面试中,数组大部分情况下都是int类型的,所以以下就用int类型来实现这些基本功能。

1、数组创建和初始化

创建一维数组的方法不同的语言不一样,如下:

数组初始化:

将int[] nums = {2,5,0,4,6,-10}这种赋值方法背下来!

2、查找一个元素

根据值是否相等进行线性查找,基本实现如下:

3、增加一个元素

        将给定的元素插入到有序数组的对应位置中,我们可以先找位置,再将其他元素整体右移,最后插入到空位置上。这里需要注意,算法必须能保证在数组的首部、尾部和中间位置插入都可以成功。推荐一种实现方式,代码如下:

问题1处,注意这里的size是从1开始编号的,表示的就是实际元素的个数。而arr.length也是从1开始的,当空间满的时候就是size=arr.length,此时就不能再插入元素了。
问题2处,只能令index=size,0或者size-1都不对。例如已有序列为{3,4,7,8},如果插入的元素比8大,例如9,假如index=0,则最后{9,3,4,7,8,}。假如index=size-1,最后结果{3,4,7,9,8}。
        除了上面的方式,还可以一开始就从后往前一边移动一遍对比查找,找到位置直接插入。从效率上看,这样更好一些,因为只遍历了一次。代码如下:

4、删除一个元素

        对于删除,不能一边从后面向前移动一边查找了,因为元素可能不存在。所以要分为两个步骤,先从最左侧开始查找是否存在元素,如果元素存在,则从该位置开始执行删除操作。例如序列是1 2 3 4 5 6 7 8 9,要删除5,则应先遍历,找到5,然后从5开始执行删除操作,也就是从6开始逐步覆盖上一个元素,最终将序列变成1 2 3 4 6 7 8 9 [9]。

三、单调数组问题

        我们在写算法的时候,数组是否有序是一个非常重要的前提,有或者没有可能会采用完全不同的策略。LeetCode896.判断一个给定的数组是否为单调数组。
分析:如果对于i<=j,A[i] <= A[j],那么数组A是单调递增的;如果对于所有i <= j,A[i] >= A[j],那么数组A是单调递减的。所以遍历数组执行这个判定条件就行了,由于有递增和递减两种情况,于是我们执行两次循环就可以了。代码如下:

优化:

        我们判断整体单调性不是白干的,很多时候需要将特定元素插入到有序序列中,并保证插入后的序列仍然有序,例如LeetCode35:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

实现代码如下:

四、数组合并专题

数组合并就是将两个或者多个有序数组合并成一个新的。
LeetCode88:给你两个按非递减顺序排列的整数数组nums1和muns2,领有两个整数m和n,分别表示nums1和nums2中的元素数目。请你合并nums2到nums1中,使合并后的数组同样按非递减顺序排序。
注意:最终合并后数组不应由函数返回,而是存储在数组nums1中。为了应对这种情况,nums1的初始长度为m+n,其中前m个元素表示应合并的元素,后n个元素为0应忽略。nums2的长度为n。

        这个问题的关键是将B合并到A后仍然要保证有序。因为A是数组不能强行插入,如果从前往后插入,数组A后面的元素会多次移动,代码比较高。此时可以借助一个新数组C来做,先将选择好的放入到C中,最后再返回。这样虽然解决了问题,但是算法上的空间复杂度为O(n)。
        比较好的方式是从后向前插入,A和B的元素数量是固定的,所以排序后最远位置一定是A和B元素都最大的那个,一次类推,每次都找最大的那个从后往前填就可以了,代码如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值