链表,作为一项基本的数据结构,简单,又复杂,在面试中经常被问到,所有有必要一文搞定。链表,是一种动态数据
结构,因为在创建链表的时候,我们不需要知道链表的长度,当插入一个结点时,只需要为该结点分配内存,
然后调整指针的指向来确保新结点被连接到链表中。
(0)链表反转
class SListNode(){
SListNode pnext=null;
int val;
SlistNode(int val){this.val = val}
}
public ListNode reverseList(ListNode head){
if(head == null) return null;
ListNode pre = null;
ListNode next = null;
while(head){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre ;
}
(1)不遍历链表删除非尾结点
思路:通常我们做链表结点删除的时候,都需要知道目标结点的前驱,让前驱结点的next指向目标结点的next,然后释放目标结点,这种方法需要遍历链表。当不能遍历时,我们就需要用后一个结点来覆盖目标结点,即:将后一个结点的data复制到目标结点中,然目标结点的next,指向后一个结点的next,返回释放后一个结点。
void RemoveNodeNotTail(SListNode **ppFirst, SListNode *pos)
{
SListNode *pDel = pos;
SListNode *pNode = pDel ->next;
pos->data = pNode->data;
pos->next = pNode->next;
free(pNode);
}
2、遍历一次,找到中间结点
只遍历一遍要找到中间节点,我们可以定义一快一慢两个指针pFast和pSlow,让他们同时从链表的头结点开始向后遍历,慢指针一次向后走一步,快指针一次向后走两步,当pFast == NULL时,pSlow所指向的位置即为链表的中间节点。
SListNode* FindMid(SListNode *pFirst)
{
SListNode *pSlow = pFirst;
SListNode *pFast = pFirst->pNext;
assert(pFirst);
while(pFast)
{
pSlow = pSlow->pNext;
pFast = pFast->pNext->pNext;
}
pSlow->pNext = NULL;
return pSlow;
}
方法2
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
}
3、遍历一次,找到倒数第K个结点(k从1开始)
原理:原理同遍历一次找到中间结点,定义一块,一慢两个指针,让快指针,先走k-1 步,然后两个指针同时向后遍历,当快指针==null时,慢指针指向的位置即为倒数第k个结点所在的位置。为了符合大多数人的习惯,本题从 1 开始计数,即链表的尾结点是倒数第 1 个结点。
public static ListNode findKthToTail(ListNode head, int k) {
// 输入的链表不能为空,并且k大于0
if (k < 1 || head == null) {
return null;
}
// 指向头结点
ListNode pointer = head;
// 倒数第k个结点与倒数第一个结点相隔k-1个位置
// pointer先走k-1个位置
for (int i = 1; i < k; i++) {
// 说明还有结点
if (pointer.next != null) {
pointer = pointer.next;
}
// 已经没有节点了,但是i还没有到达k-1说明k太大,链表中没有那么多的元素
else {
// 返回结果
return null;
}
}
// pointer还没有走到链表的末尾,那么pointer和head一起走,
// 当pointer走到最后一个结点即,pointer.next=null时,head就是倒数第k个结点
while (pointer.next != null) {
head = head.next;
pointer = pointer.next;
}
// 返回结果
return head;
}
ListNode* FindKthToTail(ListNode* pListHead,unsigned int k)
{
if(pListHead == NULL)
return NULL;
ListNode* pFast = pListHead;
ListNode* pSlow = pListHead;
for(unsigned int i=0;i<k;i++)
{
if(pFast->next)
pFast = pFast->next;
else
return NULL;
}
while(pFast->next)
{
pFast = pFast->next;
pSlow = pSlow->next;
}
retrn pSlow;
}
(4)删除倒数第K个结点
思路:找打导出第k+1 个结点,即可
void RemoveK(SListNode *pFirst, int k)
{
SListNode *pSlow = pFirst;
SListNode *pFast = pFirst;
SListNode *pDel;
while(k)
{
pFast = pFast->pNext;
k--;
}
while(pFast)
{
pSlow = pSlow->pNext;
pFast = pFast->pNext;
}
pDel = pSlow->pNext;
pSlow->pNext = pDel->pNext;
free(pDel);
}
(5)判断链表是否带环;若带环,求环的长度和入口点
要判断一个链表是否带环,只需要定义一快一慢两个指针pFast和pSlow,让他们同时从链表的头结点开始向后遍历,慢指针一次向后走一步,快指针一次向后走两步,若两个指针最终相遇,则链表带环;若快指针遍历到NULL,则表明链表不带环。
当pSlow和pFast第一次相遇时,pSlow走过的路程为l + a,pFast走过的路程为l + 2*a + c,又因为我们知道pFast的速度为pSlow的两倍,因此可得出l = c,此时我们只需要定义一个pMeet指针从头节点出发开始遍历,pSlow从相遇点出发继续遍历,当两个指针相遇时,所指向的节点即为环的入口节点。
知道了环的入口节点,那么环的长度就十分好求了。用pMeet指针记录环的入口节点,pSlow从入口节点出发向后遍历,当两个指针再次相遇时,pSlow所走过的路径即为环的长度。
void IsLoopSList(SListNode *pFirst)
{
SListNode *pSlow = pFirst;
SListNode *pFast = pFirst->pNext->pNext;
SListNode *pEnter = (SListNode*)malloc(sizeof(SListNode));
int flag = 1;
int LoopLen = 0;
while((pSlow != pFast) && (pFast != NULL))
{
//这里是因为,pFast已经走了,而low开没走,所有low也要走一步
if(flag)
{
pSlow = pSlow->pNext;
flag = 0;
continue;
}
pSlow = pSlow->pNext;
pFast = pFast->pNext->pNext;
}
if(pFast != NULL)
{
SListNode *pMeet = pSlow;
SListNode *pNode = pFirst;
while(pNode != pMeet)
{
pNode = pNode->pNext;
pMeet = pMeet->pNext;
}
pEnter->data = pMeet->data;
pEnter->pNext = NULL;
printf("该链表带环,环的入口点为:> ");
PrintSList(pEnte);
pFast = pFast->pNext->pNext;
while(pSlow != pFast){
if(LoopLen == 0)
{
pSlow = pSlow->pNext;
LoopLen++;
continue;
}
pSlow = pSlow->pNext;
pFast = pFast->pNext->pNext;
LoopLen++;
}
printf("环的长度为:> %dn", LoopLen);
return;
}
printf("该链表不带环n");
}
备注:
单链表常见面试题_Python_Dxx_xx4的博客-CSDN博客blog.csdn.net