移除链表元素
题目要求:给你一个链表的头结点 head 和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回新的头结点。
代码实现:
struct ListNode{
int val;
struct ListNode *next;
};
struct ListNode* removeElements(struct ListNode* head,int val){
struct ListNode* prev = NULL,*cur = head;
while(cur)
{
if(cur->val == val)
{
if(cur = head)
{
head = cur->next;
free(cur);
cur = head;
}
else
{
prev->next = cur->next;
free(cur);
cur = cur->next;
}
}
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
反转链表
给你一个单链表的头结点head,请你反转链表,并返回反转后的链表。
实现思想:
方法一:
直接更改地址指向上一个结点,然后再把第一个结点的next指针置空,再把头指针指向最后一个结点。
代码实现的话,就是定义上指针n1 n2 n3,然后让n1指向NULL指针,n2指向第一个结点的地址,n3指向第2个结点的位置,然后向后删除的时,先把n2的值传给n1,然后把n3的值传给n2,在让n3指向第3个结点的位置,然后再像上面一样的操作,直到n2指向第5个结点的后面的NULL时,也就是n2为空的时候,这个程序结束,结束之前n1指向的第5个结点就是这个单链表新的头,就把头指针指向第5个结点,也就是n1的值。
需要主意的是,当n2指向第5个结点的时候,n3指向后面一个的NULL,这个时候n3是NULL是不能用来解引用的。所以要if判断一下,当n3不为空的时候,才进行n3的换位。
代码实现:
struct ListNode* reverseList(struct ListNode* head){
if(head == NULL)
return NULL;
struct ListNode* n1,*n2,*n3;
n1 = NULL;
n2 = head;
n3 = head->next;
while(n2)
{
// 翻转
n2->next = n1;
//
n1 = n2;
n2 = n3;
if(n3 != NULL)
n3 = n3->next;
}
return n1;
}
方法二:
把第二个结点头插到第一个结点的前面,然后再把第三个结点头插到第二个结点的前面,如此反复就实现了使用头插的方式来实现单链表的反转。
我们先定义三个指针--cur,next,newHead。首先让newHead指向NULL,cur指向第一个结点,next指向第二个结点,然后让第一个结点的next指向newHead也就是NULL,让newHead等于cur也就是让newHead指向第一个结点,然后让cur等于next,也就是让cur指向第二个结点,然后让next指向第三个结点,然后再让cur指向的第二个结点里的next指针指向第一个结点的位置,让newHead头指针指向cur也就是第二个节点的位置,再让cur等于next指向第三个结点,next指向第四个节点,如此反复,直到cur指针指向NULL的时候结束程序。
代码实现:
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
while(cur)
{
struct ListNode* next = cur->next;
//头插
cur->next = newhead;
newhead = cur;
//往后走
cur = next;
}
return newhead;
}
其实这道题还可以用函数递归的方法解决,但是这个方法有个很大的弊端,就是当要解决的链表很大的时候递归使用的空间会很大,栈容易奔溃。
链表的中间结点
给定一个头结点head的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。(要求:只能遍历一遍单链表)
如果没有要求,只需要先遍历一遍单链表,算出单链表中结点的个数,然后再让这个个数 /2 就可以实现了。
但是,题目要求只能遍历一遍单链表,这个时候可以使用快慢指针来实现。
快慢指针实际上就是多个指针的实现,对于这个题目就是用两个指针来实现,一个是快指针--fast,一个是慢指针--fast,同样是用指针来遍历一遍单链表,慢指针一次运动一个结点,快指针一次运动两个结点。这样的好处是,即遍历了一遍单链表获得单链表的结点个数,又取得了单链表的中间结点的地址。
通过画图观察,我们得知,当链表的个数是奇数的时候,fast指针指向的是最后一个结点,而当链表的个数是偶数个时,fast指针指向的是最后一个结点的后一位,也就是NULL。
如此,这个题目也很好实现了
代码实现:
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* slow, *fast;
slow = fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
链表中倒数第k个结点
输入一个链表,输出链表中的倒数第k个结点。其中k为正数。(要求:只能遍历链表一次)
示例:
一样的使用快慢指针,对于这个题目而言,一样的是一个快指针,一个慢指针。只不过这一次不是一起走,而是一个先走k步然后另一个再走,因为倒数的第k个结点其实就是正数的第n-k个结点。我们先让快指针fast先走k个结点然后慢指针在跟着快指针一个节点一个节点的走,知道fast快指针为NULL(fast = NULL)的时候,就结束程序,这样就是实现了只遍历一遍的程序
需要注意的是,假设我们输入的k值大于链表的结点个数,这时候需要判断判一下,如果大于返回NULL。
代码实现:
struct ListNode* FindKthToTail(struct ListNode* pListHead,int k)
{
struct ListNode* fast,* slow;
slow = fast = pListHead;
while(k--)
{
//判断k是否大于链表的长度
if(fast == NULL)
{
return NULL;
}
fast = fast->next;
}
while(fast)
{
slow = slow->next;
fast = fast->naxt;
}
return slow;
}
合并两个有序的链表
将两个升序链表合并为一个新的升序链表并返回这个新的链表。新链表是通过拼接给定的两个链表的所有结点组成的。
示例:
不带头结点:
我们拿 l1 l2两个指针分别指向这两个链表的第一个结点,然后用head指针指向一个新的链表,此时这个新的链表里面是空的。我们判断一下l1和l2指针指向的结点的值的大小,把值小的结点拿出来用head指向它,然后再把移动的结点的指针(l1或者l2)指向对应链表的下一个结点,用一个tail指针指向它,另一个值大的结点的指针(l1或者l2)不动。然后再把l1和l2指向的结点的值进行如上比较,再选出以这个值小的结点移到新的链表的上一个结点之后,再让tail指针指向它,然后把这些结点串联起来。-----(尾插)
如此反复,假设有一个链表比另一个链表先空,就把另一个链表的剩余的所有结点全都尾插到新的链表的尾部。
代码实现:
struct ListNode* mergeTwpLists(struct ListNode* l1,struct ListNode* 12){
//如果其中一个链表为空就返回另一个
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
struct ListNode* head,*tail=NULL;
//先取一个小的去做第一个结点
if(l1-<val < l2->val)
{
head = tail = l1;
l1 = l1->next;
}
else
{
head = tail = l2;
l2 = l2->next;
}
while(l1 && l2)
{
if(l1->nal < l2->val)
{
tail->next = l1;
tail = l1;
l1 = l1->next;
else
{
tail->next = l2;
tail = l2;
l2 = l2->next;
}
}
if{l1)
{
tail->next = l1;
}
if(l2)
{
tail->next = l2;
}
带头结点:
struct ListNode* mergeTwpLists(struct ListNode* l1,struct ListNode* 12){
//如果其中一个链表为空就返回另一个
if(l1 == NULL)
return l2;
if(l2 == NULL)
return l1;
struct ListNode* head,*tail=NULL;
//头结点的创建
head = tail = (ListNode*)malloc(sizeof(ListNode));
while(l1 && l2)
{
if(l1->nal < l2->val)
{
tail->next = l1;
tail = l1;
l1 = l1->next;
else
{
tail->next = l2;
tail = l2;
l2 = l2->next;
}
}
if{l1)
{
tail->next = l1;
}
if(l2)
{
tail->next = l2;
}
如上所示:
带头结点的链表不需要想之前那样要先把第一个元素单独拿出来插入一遍,也就相对于如果新的链表的头结点head为空的话,要把head指针移动的第一个结点的位置,有了头结点之后就不用这样判断了。
链表的分割
现在有一个链表的头指针ListNode* pHead,给一个定值,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排序之后的链表的头指针。