__++_22_10_10

一.线性表

        认为n个具有相同特性的元素,连接成序列,是被广泛使用的数据结构。具体一点:顺序表、链表、栈、队列等,它们线性的,成一条直线,但是物理结构不一定就是连续的。

二顺序表

        物理结构也是连续的,用数组实现,支持下标随机访问,加上一些函数:初始化、增删查改、扩容检查,组成顺序表(当前是用模拟C实现),设置数组是动态的,可以动态增长。

三.链表

    模拟实现的是单链表,物理结构不连续,最后的结点指向空,基于顺序表插入和删除的时候需要挪动数据的效率问题,而且扩容可能导致的空间浪费问题,让链表能够按需申请空间,让插入删除通过修改指针指向和增加删除结点的方式。在细节上,当需要用的下一个结点,而当前指向又要改变的时候,要用一个指针记录下,否则导致野指针问题,具有的功能跟顺序表大差不差。对于尾插和尾删,时间复杂度也还是O(N),单链表除了不用挪动数据,还是需要遍历,意味着,单链表也是不能做到完美,它不支持随机访问,需要遍历。

      虽然它有各种缺陷,很多的题目默认是以单链表的方式考察,而且单链表是作为许多数据结构的子结构。

    链表有八种:是否带头、是否双向、是否循环。

      模拟实现带头双向循环链表,由于有前驱指针的存在,尾插和尾插的时间复杂度变为O(1),

头插和头删都和单链表一样为O(1),体会哨兵位,是可以在做题的时候,遇到尾插的时候用上哨兵位,这样就不用考虑链表为空要做特殊处理。

由于模拟实现单链表的时候是不带头单向非循环链表,于是,写头插、头删、尾插、尾删,涉及需要修改头指针的时候,比如头插,头指针就要改变,指向新插入的那个结点,再如头删,头指针指向的结点都删除了,那头指针就要指向原来头指针的下一个,需要传二级指针。

实现顺序表的时候,只需要传一级指针,这是因为把指针和两个辅助变量都放到结构体里是,修改里面的内容,传结构体的指针就可以了;而在单链表,结构体里,本来就有一个结构体指针,而且定义出对象的时候,直接定义的是结构体指针,要修改结构体指针指向的结构体里的内容,就需要传“它”的指针,这个它指的是定义对象是的结构体指针,那么传参的时候,就要传指针的指针,也就是二级指针。

到了带头双向循环链表的时候,结构体内就是要存储的数据和两个指针,定义对象的时候就是自定义类型,不是自定义类型的指针。如果传参不写二级指针,就需要返回。定义对象的时候,配合初始化,开辟哨兵我结点,让两个指针都指向它自己,接受返回值,接下来插入、删除的数据都是在这个哨兵位的后面,无论怎么样都不会修改哨兵位的这个结点,如果修改头结点,就是修改哨兵位的下一个,后面要实现的函数就不需要传二级指针,就像顺序表那样,要修改双链表,就传双链表的指针,这个指针是一级指针。

由于链表的结点都是按需申请的,不像数组那样,直接free掉指向数组的指针即可,链表是需要一个个遍历,保存好free掉之后需要找的指针,一个个free掉。

四.栈和队列

栈的特定是后进先出,队列的特点是先进先出。考察特点的时候,就根据特点是出栈或者出队列,判断出不符合栈后进先出特点的出栈顺序和不符合先进先出的出队列方式。

五.一些题目的思路

(1)顺序表:

        <1>

         如果不考虑空间复杂度,可以开辟一个空间与数组相同大小的空间,把不是val的元素都放到新空间,最后返回新长度,空间复杂度为O(N)。

原地解决:准备两个下标,遍历数组,当没有遇到val的时候,走得快的下标对于的值给慢的,然后同时往后走,如果相等,快的跳过。这样的话,它们就会有差距,最起码慢下标后面那个是可以被替换,遇到不相同的,就被放到可替换的那个下标,如果结束了,返回慢下标,因为每次给慢下标值都++,不需要再+1。如果遇到连续的val,可能快下标走完之后,顺序会改变。

 <2>

 num2整合到num1,说明num1的长度一定更长的,合并之后的数组也要非递减的。准备三个下标,一个是num1最后一个下标a,一个是num2最后一个下标b,一个是num1的最后一个下标c,比较a和b对应的值,谁大就填到num1的最后一个位置,c和大的下标--,a和b其中一个走完就结束,再处理未走完的,只需要处理b未走完的情况即可,因为a的下标和c的下标都是num1的,而a原本就是非递减的。如果是b为走完,而前面的遍历说明,一开始a前面和a对应的值都是可以修改的,如果a提前走完,那么大的都在后面了,只有小的可以留下来,而留下的b数组也已经是非递减的,把num2的值填到num1,它们同时结束。

<3> 

 要求O(1)的空间复杂度:准备两个下标,a为nums的开始,b为nums的下一个,如果两个下标对应的值相同,b++跳过,这也意味着被跳过的下标的那个元素是可以被替换的,这样,当不相等的时候,先把a加1,到可替换位置,把b元素给a,b再更新。如果从头到尾都没有相同的话,这个过程就是自己给自己赋值,如果从头到尾都是相同的,a没有机会+1.就保持原来的值。返回的是a+1,一般情况,下标+1就是数组长度,全是重复值的返回1。

 <4>

 如果不考虑空间复杂度,可以开辟相同长度的数组,从n-k开始到最后的值头插,在把剩下的数组尾插。

原地解决:三次逆置,0到n-1-k、n-k到n-1,0到n-1。

 (2)链表

<1>

遍历链表,如果需要val,让前一个指向当前后一个,如果空链表返回空,如果只有一个结点,等于val返回空,不等于返回即可。 保存前一个,先把cur给prev,next=cur-next,如果等于val,先free(cur),再把next给cur,或者用一个临时指针。

另一种思路:如果不等于val,因为尾插不改变顺序,拿下来尾插,如果遇到val的,用一个指针保存cur,把cur的下一个给cur,free到临时指针,直到空。尾插的时候,没有带哨兵位的情况下,而且是一定尾插,需要把cur直接给tail,不是第一个尾插,就把cur给cur的下一个,cur的下一个给cur。

 <2>

 把小的拿下来尾插,可以带也可以不带哨兵位,不带哨兵位要处理,第一个插入和空链表。带了哨兵位的话,返回的是哨兵位的下一个,两种情况都可以满足,不需要特殊处理。注意尾插完之后同时原链表和新链表的tail都要更新,如果某一个链表提前结束,直接在tail后面链接就可以了。返回的时候记录哨兵位的下一个,free掉哨兵位,返回记录的。

 <3>

 遍历链表,记录一共有多少个结点,让后找到中间的那个,时间复杂度O(N)。

另一种思路:定义慢指针和快指针,慢指针每次走一步,快指针每次走两步。快指针走到最后一个结点或者走到最后结点后的空的时候结束。当链表结点个数为偶数的时候,会走到最后一个结点或者最后一个结点的前一个位置,走完的时候慢指针就可以走到第二个中间结点。当链表结点个数为奇数个的时候,走到中间结点结束。

 <4>

定义快指针和慢指针,让快指针先走k步然后同时走,走到快指针结束,返回慢指针。

需要处理是下k不能大于链表长度的情况,直接返回空。

 <5>

可以改变指针指向,先写一个空节点newnote,链接在第一个结点后面,记录第一个结点的下一个,第一个结点链接上NULL,在把cur给newnote,这就相当于在这条链表旁边做了头插,每次都在记录下一个结点,链接上newnote,把cur给newnote,走完结束。

也可以用三个指针遍历,记录cur的前一个和cur的后一个,让cur链接前一个,在把cur给prev,把next给cur,cur到空结束,next不能也是空,本质上也是改变指向,有了另外两个指针辅助记住前后指针。

 <6>

 给两个带头的队列,一个存放小于x的,一个存放另一个的,需要提防的情况是,如果原来链表中存大于x的最后一个结点如果不是指向空,要把它链接成空,要不然它就指向原来它的下一个,变成环了。因为带头了,需要free掉头之前要把小于x的头改成哨兵位的下一个,并且它的最后链接另一个链哨兵位的下一个。

 <7>

 用上之前写的返回中间结点和链表逆置。

如果把中间之后的链表逆置了,用一个指针遍历,它和逆置之后的指针一起往后走,如果是回文,就能让逆置的那部分走到尾,也可能只有一个结点,循环条件也要加上原链表不能为空。因为逆置之后的链表示原来链表部分的拷贝,所有走完回文,再走的是里面值相等的,再走就结束了。

<8> 

 链表的特点是它只能指向一个结点,所以不是交叉。既然相交,至少最后一个结点一定相同。第一个遍历先判断它们相交,遍历找到它们的差距步,第三步,让长的先走差距步,再一起走,相等即是那个结点,注意一下确认长链表。

 <9>

使用快指针和慢指针,最后一定会相遇,无论这个环的结点个数是奇数还是偶数,进环之后,它们的差距每一次都减少一个结点,而且要求快指针每次走两步,而不是3次或者4次这些需要讨论每次缩短的差距是奇数还是偶数,更不用纠结它们在环结点是奇数和偶数能不能相遇。

<10>

结合上一题,在差距每次缩小一个结点的情况下,先找到快慢指针相同的结点,然后和一个从头开始遍历的指针一起走。因为快指针除去相遇时已经走的圈数,走剩下圈的步数为N-X,就等于慢指针进环前走的步数,想一下,按照这个假设,在相遇在入口,在走X就又回到了快慢指针相遇的点,它们同时走,速度一样。

 <11>

处理next结点:把拷贝的结点链接在原结点后面,并且拷贝结点的下一个指向原结点的下一个,遇到空就结束。这样的话,原链表的顺序就被打乱了,要恢复一下。

处理random:遍历,由于原链表指向改变,借助拷贝结点,即下一个就是拷贝结点的下一个,拷贝链表也一起遍历,把cur的random的下一个给copy的ramdom,因为cur的ramdom的下一个就是cur的rondom的拷贝,这样就完成了拷贝链表ramdom的指向,最后还要把原链表恢复,把拷贝链表的指针也恢复成正常链表的样子。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值