目录
1.移除链表元素
题目的链接在上面,这个题涉及到当链表被删除为空时返回值也为空,所以建议大家不在原链表上进行更改,我的思路是再创建两个节点类型的结构体指针(phead,ptail)然后用尾插的方式,将不等于我们链表元素的节点尾插到新的链表里面。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
struct ListNode* removeElements(struct ListNode* head, int val) {
SL* newhead , *newtail;
newhead = newtail = NULL;
SL* pcur = NULL;
pcur = head;
while(pcur)
{
if(pcur->val!=val)
{
if(newhead == NULL)
{
newhead = newtail = pcur;
}
else
{
newtail->next = pcur;
newtail = newtail->next;
}
}
pcur=pcur->next;
}
if(newtail)
{
newtail->next=NULL;
}
return newhead;
}
像这样一步一步进行尾插最后返回phead就完成了。
2.反转链表
我们需要把链表进行改方向把链表的顺序颠倒,这里提供两个方法:一个是三指针法,头插入法。
2.1三指针法
我们需要定义三个链表结构体指针n1,n2,n3,我们将n1指向链表第一个节点,n2指向第二个节点,n3指向第三个节点,我们将n2->next指向n1,再n1指向n2,n2指向n3,n3指向n3->next,依次这样就可以反转链表了。
我们在设计这个循环的时候我们应该的进入循环的条件应该是n3指针不为空指针,在要注意的有两点:
1.n1->next一定要指向NULL;
2.判断是否结束循环条件的时候一定要注意n3是否越界访问。
思路如上图所示:
代码如下所示:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode listnode;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL||head->next == NULL)
{
return head;
}
listnode *n1 = head;
listnode *n2 = head->next;
listnode *n3 = head->next->next;
n1->next = NULL;
n2->next = n1;
while(n3)
{
n1 =n2;
n2 = n3;
n3 = n3->next;
n2->next = n1;
}
return n2;
}
2.2头插法
我们定义两个空指针(phead,ptail),再定义一个指针pcur记录原链表节点位置,我们采用第一题的思路我们选择头插来插入数据这样也可以实现。
像这样循环就可以将链表反转过来。
代码如下所示:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode LN;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL||head->next == NULL)
{
return head;
}
LN*pcur = head;
LN*phead = NULL;
LN*ptial = NULL;
while(pcur)
{
if(phead==NULL&&ptial == NULL)
{
phead = ptial = pcur;
}
else
{
LN*temp = pcur;
pcur = pcur->next;
temp->next = phead;
phead = temp;
}
}
ptial->next = NULL;
return phead;
}
3.分割链表
这道题我们的思路就是把链表分为两个,一个存放小于X的节点,一个存放大于或等于X的节点,再把小链表的尾和大链表的头连在一起最后返回小链表的头。
如上图一样循环,再把greatphead和lessptail链表连接起来就行了。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
struct ListNode* partition(struct ListNode* head, int x){
if(head == NULL)
{
return head;
}
SL* greaterhead,*greatertial,*lesshead,*lesstial;
SL* pcur;
pcur = head;
greaterhead = greatertial = (SL*)malloc(sizeof(SL));
lesshead = lesstial = (SL*)malloc(sizeof(SL));
while(pcur)
{
if(pcur->val < x)
{
lesstial->next = pcur;
lesstial = lesstial->next;
}
else
{
greatertial->next = pcur;
greatertial = greatertial->next;
}
pcur = pcur->next;
}
greatertial->next = NULL;
lesstial->next = greaterhead->next;
SL* ret = lesshead->next;
free(lesshead);
free(greaterhead);
return ret;
}
4.链表的中间节点(快慢指针)
4.1快慢指针
快慢指针在不同的时候会有不同的使用方法表明的是一种思像实质就是用链表的遍历进程来抽象数学问题。下面有几道例题。
4.2求链表的中间节点
我们在看到这个题的时候容易想到的方法是先遍历链表同时计数,然后再除2得到中间节点的位置是第几个元素,再从头找到该位置的节点并返回。
这个方式我们可以看出时间复杂度为O(N+N/2),也就是O(N)。当链表特别长的时候我们会发现这样太复杂了浪费时间。
我们思考一个数学问题:汽车甲的速度是10m/s,汽车乙的速度是20m/s,两辆汽车同时启动行驶1km当一辆汽车到达终点的时候另一辆汽距离终点还有多远?
答案很轻松就可以知道是500米。
我们把这个问题抽象到这道题里面就产生了我们的快慢指针这个概念。
实现步骤如下图所示:
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
struct ListNode* middleNode(struct ListNode* head) {
SL* fast,*slow;
fast = slow = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
这里需要注意的是我们的while循环条件要注意先后顺序一定是fast && fast->next,如果fast->next在前面会出现越界访问。
5.环形链表
环形链表中也会使用到快慢指针的思想
5.1环形链表是否成环
这里就好比我们同学在操场上面进行体育测试有些跑得快的同学可以套跑的慢的同学一圈,被套了一圈就说明两个同学一定相遇过一次。我们根据这个方法也可以使用快慢指针来实现这个过程。
我们让一个指针一次走一步另一个一次走两步如果两个指针相遇了就返回true没有相遇就返回false。
问题:为什么是一个走一步一个走两步为什么不能是其他步数?
答 :在成环的时候我们的指针是不是要保证一定需要相遇,能一定满足这个条件的就是一个走两步一个走一步因为在整数当中只有奇数和偶数两种就表明在当慢指针入环时第一圈不能相遇的话第二圈一定会相遇,这样我们就保证在能发现它是环形链表的同时走尽量少的步数。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode SL;
bool hasCycle(struct ListNode *head) {
struct ListNode*fast,*slow;
fast = slow = head;
while(fast&&fast->next)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
return true;
}
}
return false;
}
5.2环形链表入环节点
我们利用快慢指针先来找一下里面的数学关系:
经过上图所找到的关系我们可以设计我们的程序 。
我们先用快慢指针的方法找到相交节点再用一个指针从头开始,一个指针从相交节点开始都走一步两个节点都指向同一个节点的时候就可以确定它是入环节点。
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head){
if(head==NULL)
return false;
struct ListNode *fast=head,*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
fast=head;
while(1)
{
if(fast==slow)
{
return fast;
}
fast=fast->next;
slow=slow->next;
}
}
}
return NULL;
}
5.3入环节点推论的不完备性说明
我们看见上面的例子举的其实是一个特殊例子:
就是这句话 我们才得出的L= R-H这个结论,但是当我们的入环节点前的链表长度远远大于环形链表的长度的时候fast一定是在里面走了多圈才相遇的,我们应该引入变量n (n>=1)来表示走的圈数。
所以结论应该是:
L = nR - H。
这道题的测试用例就只讨论了我们前面讨论的特殊情况。