Bootstrap

链表

基本概念

链表是面试时被提及最频繁的数据结构。链表的结构很简单,它由 指针 把若干个节点连接成链状结构。

链表是一种 动态 数据结构,因为在创建链表时,无须知道链表的长度。由于没有闲置的内存, 链表 的空间效率比 数组 高。

链表的内存 不是一次性分配 的,所以链表的内存不是和数组一样连续。因此寻找某个节点 i i i 的时间效率为 O ( n ) O(n) O(n)

例题

单向链表

面试题6“从头到尾打印链表”

在这里插入图片描述

简单方法:翻转链表
不能改变结构:使用 递归 在本质上就是一个 结构。每访问到一个节点的时候,先递归输出它后面的节点,再输出该节点自身。

面试题18“删除链表的节点”
在这里插入图片描述
这题没太多问题,用 双指针 ,一个记录当前节点,一个记录上一节点就可以了。要删除当前节点的时候,把下一节点续在上一节点后面就okk。

面试题22“链表中倒数第k个节点”

在这里插入图片描述
这个题最简单的办法就是 两次遍历 ,第一次算出链表长度,第二次找到第 n − k + 1 n-k+1 nk+1个节点返回。

但是这方法显然不是最好的,还能怎么办呢?

可以 一次遍历 就解决这个问题。

用两个指针来记录,第一个指针就正常遍历这个链表,第二个指针记录第一个指针的倒数第k个节点。也就是说,当第一个指针到了第k个节点的时候,第二个指针指向head,此后两个指针始终保持k-1的距离往后遍历,知道第一个指针指向最后一个节点,此时第二个指针就是倒数第k个,返回即可。

BUT :以上能够在leetcode上AC,但书里说这样还不够,它处在《3.4 代码的鲁棒性》这一章节中……意思是你得多考虑考虑有没有什么问题。书中说这个代码虽然简单,但是存在3个潜在崩溃的风险:

  1. 输入的链表是空的
  2. k是0
  3. 链表节点数小于k

在解题时需要对这些情况进行考虑和处理。

面试题23“链表中环的入口节点”(leetcode中无此题)

题目:如果一个链表中包含环,如何找出环的入口节点?

  1. 如何确定一个链表中包含环?
    可以用 两个指针 。定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就包含环;如果走得快的指针走到了链表的末尾(m_pNext指向NULL)都没有追上第一个指针,那么链表就不包含环。

  2. 如何找到环的入口
    还是用 两个指针 。定义两个指针 P 1 P_1 P1 P 2 P_2 P2 指向链表的头节点。如果链表中的环有n个节点,则指针 P 1 P_1 P1 先在链表上向前移动 n n n 步,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口节点时,第一个指针已经围绕着环走了一圈,又回到了入口节点。

  3. 如何得到环中节点的数目
    在前面提到判断一个链表是否有环时用到了一快一慢两个指针。如果两个指针相遇,则表明链表中存在环。两个指针 相遇的节点 一定是在环中。可以从这个节点出发,一边继续向前移动一遍计数,当再次回到这个节点时,就可以得到环中节点数了。

面试题24“翻转链表”

在这里插入图片描述
用 1,2,3 三个指针记录就行了,1和2要进行反向,最开始的时候1的next是None,然后3的作用是在2的next变成了1之后还能找到 原来 2的next。接下来a=b,b=c,c=c.next就可以了,如果c没next直接退出循环就行了。

注意特殊情况比如说整个链表长度是0或者1之类的,处理一下就okk

面试题25“合并两个排序的链表”

在这里插入图片描述
这题好说,递归就行了。。

当我们得到两个链表中值较小的头结点并把它链接到已经合并的链表之后,两个链表剩余的节点依然是排序的,因此合并的步骤和之前的步骤是一样的。这就是典型的 递归 过程,我们可以定义递归函数完成这一合并过程。

面试题52“两个链表的第一个公共节点”

在这里插入图片描述
这个题有几种方法:

  1. 用两个栈来分别存储两个链表的节点,这样两个链表的尾节点就位于两个栈的栈顶,接下来比较两个栈顶的节点是否相同。如果相同,则把栈顶弹出接着比较下一个栈顶,直到找到最后一个相同的节点。
  2. 首先遍历两个链表得到他们的长度m和n,假设m是短的那个。在第二次遍历的时候,让n先走m-n步,然后俩人同时开始遍历,找到的第一个相同的节点就是它们的第一个公共节点。
  3. 我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。
    这样,当它们相遇时,所指向的结点就是第一个公共结点。
    我寻思这就是最小公倍数的意思吧。。。代码倒是真简单。。。
class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        node1, node2 = headA, headB
        
        while node1 != node2:
            node1 = node1.next if node1 else headB
            node2 = node2.next if node2 else headA

        return node1

环形链表

面试题62“圆圈中最后剩下的数字”

  1. 经典解法,用环形链表模拟圆圈
  2. 找规律,“脑筋急转弯”

双向链表

面试题36“二叉搜索树与双向链表”

面试题35“复杂链表的复制”

在这里插入图片描述
抖个机灵:

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        from copy import deepcopy
        return deepcopy(head)

哈哈哈这样估计现场面试就凉了。

然后真正的解法。。。。。emm。。。。。太难了好叭!
三步走

  1. 对应每个节点创建一个新的链表,然后把新的链表对应位置链接在原链表后面,什么意思呢,就是本来它长成了ABCDE,现在复制出来个A’B’C’D’E’,然后对位链接成了个AA’BB’CC’DD’EE’,这个样子。。
  2. 如果A的random指向了C,那么A’的random也就指向了C’,这倒是真挺好理解的。。。照做就行了
  3. 把这个链表拆分成两个,奇数位的就是原始链表,偶数位的就是复制后的链表了。
    这挺好理解的,也不难实现,但这也太难想到了吧。。。抗议!

参考链接

1. https://lucifer.ren/blog/2020/11/08/linked-list/

;