- 由指针把若干个结点连接成链状结构。它是一种动态结构,因为在创建链表时无须知道链表长度。
- 插入一个结点时只需要为新结点分配内存,然后调整指针的指向即可。
- 如果链表的头指针会有更改,那么参数应该设为指向指针的指针,即ListNode **pHead。
struct ListNode { int m_nKey; ListNode *m_pNext; };
- 优点:空间效率高,因为是每添加一个结点才分配一次内存,所以没有闲置的内存。插入和删除效率高O(1);
- 缺点:时间效率低,即查找效率低O(n);
链表元素的删除
题目:在链表中找到第一个含有某值的结点并删除该结点;
- void RemoveNode(ListNode **pHead, int value);
解题思路:
(1)若头结点的值 = value,则删除头结点,更改头指针指向头结点的下一个结点;
(2)依次遍历,找到要被删除结点的上一个结点,调整指针的指向;
从尾到头打印链表(剑指offer---面试题5)
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值;
- void PrintListReversingly_Iteratively(ListNode *pHead);
- void PrintListReversingly_Recursively(ListNode *pHead);
解题思路:
(1)迭代法:用栈作辅助空间;
(2)递归法:每次访问结点的时候,先递归输出它后面的结点,再输出该结点本身;
在O(1)时间删除链表结点(剑指offer---面试题13)
题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点;
- void DeleteNode(ListNode **pListHead, ListNode *pToDeleted);
解题思路:
(1)若pToDeleted不是尾结点,则把该pToDeleted的下一个结点pNext的值和指针赋值给pToDeleted,然后删除pNext;
(2)若pToDeleted是尾结点且链表只有一个结点,则删除头结点,更改头指针*pListHead的值;
(3)若pToDeleted是尾结点且链表有多个结点,则从头遍历找到pToDeleted的前一个结点,然后删除pToDeleted;
链表中倒数第k个结点(剑指offer---面试题15)
题目:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点;
- ListNode *FindKthToTail(ListNode *pListHead, unsigned int k);
方法一:遍历两次,第一次找到链表中结点的个数n,第二次从头开始找到第n+1-k个结点即是倒数第k个结点;
解题思路:两个指针P1和P2;
(1)两个指针P1和P2都指向第一个结点,然后P2向后走k-1步;
(2)P1和P2同时向后移动,当P2走到末尾的时候,P1即是倒数第k个结点;
(3)边界情况:头指针pListHead为空、k=0(k-1=0xFFFFFFFF)、k < 链表结点个数n;
相关题目:求链表的中间结点、判断一个单向链表是否形成了环形结构;(两个指针P1和P2,P1一次走一步,P2一次走两步)
反转链表(剑指offer---面试题16)
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点;
- ListNode *ReverseList(ListNode *pHead);
解题思路:
(1)初始化pPre = NULL, pNode = pHead, pNext = NULL;
(2)从头开始遍历每一个结点pNode,并保存pNext = pNode->m_pNext,调整pNode的指针即pNode->m_pNext = pPre;
(3)重新更新pPre和pNode的值,重复第二步;
(4)直到遍历到最后一个结点即pNode->m_pNext == NULL时,那么pReverseHead = pNode;
合并两个排序的链表(剑指offer---面试题17)
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如链表1是1->3->5->7,链表2是2->4->6->8,那么合并后的升序链表3是1->2->3->4->5->6->7->8;
- ListNode *Merge(ListNode *pHead1, ListNode *pHead2);
解题思路:
(1)若pHead1为空,则pMergeHead = pHead2;反之若pHead2为空,则pMergeHead = pHead1;
(2)若pHead1 < pHead2,则pMergeHead = pHead1,然后再递归排序Merge(pHead1->m_pNext, pHead2);
(3)若pHead1 > pHead2,则pMergeHead = pHead2,然后再递归排序Merge(pHead1, pHead2->m_pNext);
复杂链表的复制(剑指offer---面试题26)
题目:请实现函数复制一个复杂链表。在复杂链表中,每个结点除了有一个m_pNext指针指向下一个结点,还有一个m_pSibling指向链表中的任意结点或者NULL;
- ComplexListNode *ReconnectNodes(ComplexListNode *pHead);
方法一:第一步复制原始链表的每一个结点并用m_pNext连接起来;第二步设置每个结点的m_pSibling指针O(n^2);
缺点:时间复杂度太高O(n^2);
方法二:第一步复制原始链表的每一个结点并用m_pNext连接起来,把<N, N'>的配对信息保存到一个哈希表中;第二步根据哈希表来设置每个结点的m_pSibling指针;
缺点:需要空间复杂度O(n);
解题思路:
(1)根据原始链表的每个结点N创建对应的N',并把N’连接到N的后面;
(2)设置复制出来的结点N‘的m_pSbiling;
(3)把该链表拆分成两个链表,即奇数位置的结点就是原始链表,偶数位置的结点就是复制出来的链表;
二叉搜索树和双向链表(剑指offer---面试题27)
详情见“树”专栏;
两个链表的第一个公共结点(剑指offer---面试题37)
题目:输入两个链表,找出它们的第一个公共结点;
- ListNode *FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2);
方法一:两个辅助栈分别存放两个链表的每个结点,接下来比较两个栈顶的结点是否相同,若相同则出栈,直到找到最后一个相同的结点;
缺点:需要空间复杂度O(m+n);
解题思路:两个指针P1和P2;
(1)分别遍历两个链表可以得到它们的长度,假设较长链表的长度为long,较短链表的长度为short,长度的差值diff = long - short;
(2)指针P1在long链表先走diff步,然后P1和P2同时走,若P1 == P2则该结点为两个链表的第一个公共结点;
圆圈中最后剩下的数字(剑指offer---面试题45)
题目:0,1...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里面删除第m个数字,求出这个圆圈里剩下的最后一个数字。例如0,1,2,3,4这5个数字组成一个圆圈,如果从0开始每次删除第3个数字,则删除的前四个数字依次是2、0、4、1,因此最后剩下的数字是3;
- int LastRemaining(unsigned int n, unsigned int m);
解题思路:构建辅助链表;
(1)构建辅助链表list,先将数组中的左右元素push_back到list中;
(2)遍历整个链表,找到第m个位置current并保存next = ++current;
(3)然后删除current并设置current = next继续遍历;(注意边界条件即遍历到list.end()时要重新设置为list.begin())
(4)直到list中只有一个元素为止即退出while循环;
链表环中的入口结点(剑指offer---面试题56)
题目:一个链表中包含环,如何找出环的入口结点?
- ListNode *EntryNodeOfLoop(ListNode *pHead);
解题思路:两个指针P1和P2;
(1)找到环中某个结点:构建两个P1和P2,P1一次走一步,P2一次走两步,若干次后若P1 == P2则表示在环中相遇,返回该相遇结点;
(2)计算环的长度n:相遇结点一直向前移动,直到再次回到这个结点就可以得到环中结点个数了;
(3)找到入口结点:P1和P2都从头结点开始,P1先走n步,然后P1和P2同时走,若干次后若P1 == P2则表示在环的入口结点相遇,返回该结点即可;
新方法:首先同步骤1即找到环中的相遇结点pMeetNode,然后设置P1 = pHead,P2 = pMeetNode,两个指针同时走第一次相遇的结点就是环的入口结点;
原理:假设头结点到入口结点的距离是a,相遇结点到入口结点的距离是b,环的长度是n。步骤1中当P1和P2相遇时,此时P1走了a+b步,P2走了a+b+kn=2*(a+b),那么a+b=kn,所以从头结点开始走a步刚好到达入口结点,而且从相遇结点开始走a步也刚好到达入口结点;
删除链表中重复的结点(剑指offer---面试题57)
题目:在一个排序的链表中,如何删除重复的结点?例如链表1-2-3-3-4-4-5,删除后是1-2-5;
- void deleteDuplication(ListNode **pHead);
解题思路:
(1)初始化pPreNode = NULL;
(2)遍历每一个结点pNode,若pNode的值 == pNext的值,则从pNode开始遍历并删除和pNode的值相同的结点;
(3)删除结点后,若删除结点是头结点则更新头结点pHead的值,否则连接下一个没有重复的结点即pPreNode->m_pNext = pNext;