【剑指Offer】面试题:链表

剑指Offer中一共有9道关于链表的面试题

如何在JS中创建一个链表

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

题目描述:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

方法一:栈(推荐)

思路:遍历链表,把链表节点从头到尾依次入栈;依次输出栈顶元素,并弹出栈顶元素。

方法二:递归

思路:每访问到一个节点的时候,先递归输出它后面的节点,再输出该节点自身。

面试题18:删除链表的节点

题目描述:在O(1)时间内删除链表节点。

思路:对于n-1个非尾节点而言,我们可以在O(1)时间内把下一个节点的内存复制覆盖到要删除的节点,并删除下一个节点;对于尾节点而言,我们仍然需要按顺序查找,如果当前节点的下一个节点是要删除的节点,那么就可以把当前节点的pNext指向要删除节点的下一个节点(这里即为null),此时时间复杂度是O(n)。因此总的时间复杂度是[(n-1)XO(1)+O(n)]/n,结果还是O(1)。

如果链表中只有一个节点,而我们又要删除链表的头节点(尾节点)。那么,我们在删除节点之后,还要把链表的头节点设置为null。

拓展训练:删除链表中重复的节点

题目描述:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

思路:如果链表的头节点重复,那么就一直循环直到找到一个具有不同值的节点,再把这个节点作为新链表的头节点参与递归;如果链表的头节点没有重复,那么继续进行下一个节点的判断,同样把这个节点作为参数输入参与递归,将返回值赋给新链表的下一个节点,最后返回链表的头节点。

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function deleteDuplication(pHead)
{
    if(pHead === null) return null;
    if(pHead !== null && pHead.next === null) return pHead;
    
    var current;
    if(pHead.val === pHead.next.val){
        current = pHead.next.next;
        while(current !== null && current.val === pHead.val){
            current = current.next;
        }
        return deleteDuplication(current);
    } else {
        current = pHead.next;
        pHead.next = deleteDuplication(current);
        return pHead;
    }
}

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

题目描述:输入一个链表,输出该链表中倒数第k个结点。

思路:定义两个指针,距离相差k-1(即先出发的指针提前移动k-1步),当先出发的指针到达链表的末尾时,后出发的指针正好在链表的倒数第k个节点。

拓展训练:求链表的中间节点。

思路:定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。

面试题23:链表中 环的入口节点

题目描述:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路:

第一步:如何确定一个链表中包含环?定义两个指针,一个指针一次走一步,另一个指针一次走两步,如果走得快的指针追上了走得慢的指针,说明链表中有环;如果走得快的指针走到了链表的末尾(next指向null)都没有追上走得慢的指针,那么链表中不包含环。

第二步:如何找到环的入口?同样定义两个指针P1和P2,如果链表中的环有n个节点,则指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动。当P2指向环的入口节点时,P1已经围绕环走了一圈,又回到了入口节点。

第三步:如何得到环的节点数?从第一步中两个指针相遇的节点开始,继续向前移动,一边移动一边计数,当再次回到这个节点的时候,就可以得到环的节点数了。

面试题24:反转链表

题目描述:输入一个链表,反转链表后,输出新链表的表头。

思路:初始化,定义变量pPrev为null,pNode为pHead,新链表的表头为null。每访问到一个节点(pNode)的时候,将该节点(pNode)原本指向的下一个节点(pNode.pNext)赋值给变量pNext;然后,将该节点的下一个节点(pNode.pNext)指向它的前一个节点pPrev;最后再将pNode赋值给pPrev,将pNext赋值给pNode;以此循环,直到pNode为空,返回新链表的表头(即原来链表的末尾节点)。

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

题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

思路:一个链表定义一个指针,比较两个指针的大小,小者则为合并链表的当前节点,再通过递归调用,依次找出合并链表的下一个节点,返回合并链表的表头。

面试题35:复杂链表的复制

题目描述:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

方法一:直观解法

思路:
第一步:复制原始链表上的每个节点,并用 m_pNextl 链接起来。
第二步:复制特殊指针。从头遍历复制链表,寻找特殊指针。

方法二:借助哈希表

思路:
第一步:复制原始链表上的每个节点,并用 m_pNextl 链接起来。同时把<N,N'>的配对信息放入哈希表中。
第二步:复制特殊指针。直接在哈希表中寻找配对信息。

方法三:(推荐)

思路:
第一步:根据原始链表的每个节点N创建对应的N‘。这一次,我们把N’链接在N的后面。
第二步:复制特殊指针。
第三步:把长链拆分成两个链表:把奇数位置的节点用m_pNextl 链接起来就是原始链表,把偶数位置的节点用m_pNextl 链接起来就是复制出来的链表。

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

题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

基础知识:
在搜索二叉树中,左子节点的值总是小于父节点的值,右子节点的值总是大于父节点的值,即二叉树的中序遍历。
双向链表的首节点的前一个节点为 null,尾节点的后一个节点为 null,以此判断首尾节点。

思路:
我们把二叉树看成3部分:左子树,根节点和右子树。根据排序链表的定义,我们将根节点与左子树的最大一个节点链接起来,同时与右子树的最小一个节点链接起来。

按照中序遍历的顺序,当我们遍历转换到根节点时,左子树已经转换成一个排序的链表了,并且处于链表中的最后一个节点是当前值最大的节点。此时,我们将其与根节点链接,这样链表中的最后一个节点就是根节点了。然后我们去遍历转换右子树,并把根节点和右子树的最小一个节点链接起来。

其中,转换子树用递归;初始化链表的尾节点为 null ;最后返回链表的首节点。

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

题目描述:输入两个链表,找出它们的第一个公共结点。

方法一:两个辅助栈

如果链表的长度分别为m和n,那么空间复杂度和时间复杂度都是O(m+n)。

思路:首先,分别把两个链表入栈;然后,比较两个栈的栈顶元素,若相同,则弹出栈顶元素,直到第一个不相同的元素,说明它们指向的下一个节点即为两个链表的第一个公共节点。

方法二:(推荐)

如果链表的长度分别为m和n,那么时间复杂度是O(m+n),但是不再需要额外的空间。

思路:首先,分别遍历两个链表,得到它们的长度m、n (m>n);然后,让长链表的指针提前移动 m-n 步;接着,两个指针同时移动,比较它们的大小,若不相同,则继续移动,若相同,则说明这两个指针指向的是同一个节点(即为两个链表的第一个公共节点)。

END

猜你喜欢

转载自blog.csdn.net/Dora_5537/article/details/90665202