反转链表
题目链接点这里
三指针:
解题思路:
-
先分析特殊情况,当链表为空或者只有一个结点时,直接返回head即可
-
当链表的结点多于一个时,我们首先考虑到是否可以将指针的指向依次反过来,从而实现链表的反转,如下图:
那么要将指针的指向反转过来,可以先定义两个指针n1和n2
n2->next=n1;
但这里有个问题,当n2指向n1时,第二个结点的地址丢失了,因此,还要多定义一个指针n3用于保存第二个结点的地址
-
当实现第一个结点指向NULL后,n1,n2,n3均向后移动一个结点,使第二个结点指向第一个结点
这里三个指针的移动使用迭代完成,总共分为三步:
- n1=n2
- n2=n3
- n3=n3->next
-
重复第三步,直至n2指向NULL时,循环结束,反转链表完成
这里要特别注意的是,由于是现在进行反转,在迭代,因此当n3指向NULL时,还要进行一次迭代,循环才会结束
完整代码
struct ListNode* reverseList(struct ListNode* head){ if(head==NULL||head->next==NULL) return head; struct ListNode* n1=NULL,*n2=head,*n3=head->next; while(n2) { n2->next=n1; n1=n2; n2=n3; if(n3) n3=n3->next; } return n1; }
头插法:
解题思路:
-
除了从原来的链表上实现反转,还可以新开辟一个链表,指针newhead指向表头,利用头插法,依次将原来链表的结点头插到新的链表中
struct ListNode* newhead = NULL;
-
先定义两个指针cur和next,cur用于从旧链表中取结点头插在新链表中,next同于保存下一个结点,以防cur将结点移动到新链表后,无法重新找到旧链表
-
当cur==NULL时,链表完成反转,具体过程如下:
完整代码
struct ListNode* reverseList(struct ListNode* head){ struct ListNode* newhead = NULL; //开辟新链表 struct ListNode* cur = head; //cur用于从旧链表中取结点头插在新链表中 while(cur) //cur不为NULL时,循环继续 { struct ListNode* next=cur->next; //用于保存cur移动结点的下一个结点 cur->next=newhead; //头插到新链表中 newhead=cur; //newNode指向新链表中新的头结点 cur=next; //cur回到旧链表中,继续移动下一个结点 } return newhead; }
链表的中间节点
题目链接点这里
解法:快慢指针
- 根据题目的意思,可知遍历链表后,需返回中间结点
- 那么是否可以定义两个指针,当快的指针遍历结束后,慢的指针恰好指向链表的中间结点
- 不难发现,只要快的指针每次比慢的指针多走一步,结束时,慢的指针就能指向链表的中间结点
完整代码
struct ListNode* slow=head;
struct ListNode* fast=head; //快慢指针起始位置均指向头结点
//当fast==NULL(结点个数为奇数),fast->next==NULL(结点个数为偶数),循环结束,找到中间结点
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next; //慢指针走一步,快指针走两步
}
return slow;
链表中倒数第k个节点
题目: 输入一个链表,输出该链表中倒数第k哥结点。为了符合大多数人的习惯,本题从1开始计数,即 链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1,2,3,4, 5,6。这个链表的倒数第3个结点是值为4的结点。
解题思路:
- 这题和上一题一样,也是要求返回链表中的某个结点,因此,同样可以用快慢指针的方法来解决
- 定义快慢指针,但这里稍微不同的是快指针起始位置指向NULL,慢指针指向头结点,快指针提前走k步,然后快慢指针再同时向前移动,那么快指针将一直领先慢指针k个节点,在快指针遍历链表后,慢指针指向的结点就是链表中倒数第k个结点。
struct ListNode*fast=NULL; //快指针指向NULL
struct ListNode*slow=pHead; //慢指针指向头结点
while(k--) //快指针先走k步
{
if(fast)
fast=fast->next;
else
return NULL;
}
while(fast) //快慢指针同时移动
{
fast=fast->next;
slow=slow->next;
}
return slow;
合并两个有序链表
-
首先考虑特殊情况,若链表都为空则返回其中一个链表即可,若其中一个为空,则返回另一个不为空的链表
if(list1==NULL) { return list2; } if(list2==NULL) { return list1; }
-
除了第一种特殊情况外,其他情况可以malloc一个带哨兵位的头结点,依次比较list1和list2的结点大小,小的结点优先尾插到新链表,要进行尾插,则要定义一个指向新链表尾结点的指针,记录尾结点的地址
Node*head=NULL,*tail=NULL; head=tail=(Node*)malloc(sizeof(Node)); //取小的尾插 while(list1&&list2) { if(list1->val<list2->val) { tail->next=list1; list1=list1->next; } else { tail->next=list2; list2=list2->next; } tail=tail->next; }
-
何时结束?当其中一个链表为空时,将另一个链表的剩余部分整体尾插到新链表之后即可
if(list1) tail->next=list1; else tail->next=list2;
整体流程如下:
完整代码
typedef struct ListNode Node;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1==NULL) //特殊情况判断
{
return list2;
}
if(list2==NULL)
{
return list1;
}
Node*head=NULL,*tail=NULL;
//带哨兵位的头结点,不储存有效数据
head=tail=(Node*)malloc(sizeof(Node));
//取小的尾插
while(list1&&list2) //两个链表均不为空
{
if(list1->val<list2->val)
{
tail->next=list1;
list1=list1->next;
}
else
{
tail->next=list2;
list2=list2->next;
}
tail=tail->next;
}
if(list1)
tail->next=list1;
else
tail->next=list2;
Node *realHead;
realHead=head->next; //真正的头结点是哨兵位的下一个结点
return realHead;
}
链表分割
思路:
由于在原来链表的基础上无法对链表进行重新排序,那么既然题目要求将比x大的移到x结点之后,比x小的移到x结点之前,我们可以开辟两个链表,一个用来储存比x小的结点(smallhead),一个用来储存比x大的结点(bighead),然后将两个链表连接起来即可。
- 遍历原链表,比x小的结点尾插到smallhead,比x大的头插到bighead,最终得到如下两个链表。
- 这里需要注意,由于开辟的链表的头结点是哨兵结点,不储存数据,两个链表链接时,应该是大链表的哨兵位头结点的后一个结点与小链表尾结点链接,大链表尾结点再置空,这样就得到了分隔后的链表。
smalltail->next=bigHead->next;
bigtail->next=NULL;
完整代码
typedef struct ListNode Node;
struct ListNode* partition(struct ListNode* head, int x){
Node *smallHead,*smalltail,*bigHead,*bigtail;
smallHead=smalltail=(Node*)malloc(sizeof(Node));
bigHead=bigtail=(Node*)malloc(sizeof(Node));
smallHead->next=smalltail->next=NULL;
bigHead->next=bigtail->next=NULL;
while(head)
{
if(head->val<x)
{
smalltail->next=head;
smalltail=smalltail->next;
}
else
{
bigtail->next=head;
bigtail=bigtail->next;
}
head=head->next;
}
smalltail->next=bigHead->next;
bigtail->next=NULL;
return smallHead->next;
}
回文链表
思路:
-
判断链表是否为回文链表,我们可以从中间结点处断开,再逐一对比每个结点,而如何找到中间结点,依然可以用快慢指针的方法,慢指针每走一步,快指针就走两步,当快指针走到尾时,慢指针指向的就是中间结点,但我们不是要在中间结点后断开,而是要在中间结点的前一个结点断开,那么我们还需要一个指针来保留慢指针的前一个结点。
-
但由于链表不能逆向遍历,因此还要对第二个链表进行反转,反转的方法和上面链表反转题目的第二种方法一样,这里就不赘述了。
完整代码
typedef struct ListNode Node;
bool isPalindrome(struct ListNode* head){
Node *fast = head;
Node *slow = head;
Node *prev = NULL;
if(head->next == NULL)
return true;
while(fast && fast->next)
{
prev = slow; // 保留slow的前一个结点
slow = slow->next; //快指针走两步
fast = fast->next->next; //快指针走两步
}
prev->next = NULL;//从prev结点后断开
//链表反转
Node *newhead = NULL;
Node *cur = slow;
while(cur)
{
Node *next =cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
//两个链表进行比较
while(head)
{
if(head->val == newhead->val)
{
head = head->next;
newhead = newhead->next;
}
else
return false;
}
return true;
}
e *newhead = NULL;
Node *cur = slow;
while(cur)
{
Node *next =cur->next;
cur->next = newhead;
newhead = cur;
cur = next;
}
//两个链表进行比较
while(head)
{
if(head->val == newhead->val)
{
head = head->next;
newhead = newhead->next;
}
else
return false;
}
return true;
}