1. 删除链表中等于给定值 val 的所有结点。 https://leetcode.cn/problems/remove-linked-list-elements/description/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode *tail=NULL,*newhead=NULL;//如果头结点的值为val需要重新改newhead
struct ListNode *cur=head;
while(cur)//遍历整个链表
{
if(cur->val!=val)
{
if(tail==NULL)
{
tail=newhead=cur;
}
else
{
tail->next=cur;//如果上一个被删除,需要重新连接节点不是要删除的节点
tail=tail->next;
}
cur=cur->next;
}
else
{
struct ListNode *tmp=cur->next;//把要删除的节点的下一个节点储存起来
free(cur);//删除值为val的节点
cur=tmp;//再将刚刚存储的节点赋值过来
}
}
if(tail)
tail->next=NULL;//把最后的tail的next置为NULL
return newhead;
}
2. 反转一个单链表。
https://leetcode.cn/problems/reverse-linked-list/description/
struct ListNode* reverseList(struct ListNode* head)
{
if(head==NULL)
return NULL;
struct ListNode* newhead,*cur;
cur=head;
newhead=NULL;
while(cur)//遍历整个链表,当最后一个节点走完退出循环
{
struct ListNode* next=cur->next;//先将cur的下一个节点保存起来
cur->next=newhead;//newhead重新赋值给下一个cur的next,newhead刚开始为NULL,后面依次赋值为cur
newhead=cur;//newhead向右移一位
cur=next;//将next赋值给cur
}
return newhead;
}
3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
https://leetcode.cn/problems/middle-of-the-linked-list/description/
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode *slow,*fast;
slow=fast=head;
while(fast&&fast->next)//单数时候fast-next为null跳出,双数的时候fast为null跳出
{
slow=slow->next;//slow走一步
fast=fast->next->next;//fast走两步
}
return slow;
}
4. 输入一个链表,输出该链表中倒数第k个结点。
https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&&tqId=11167&rp=2&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking
解题思路:
倒数第k个走到最后一个一共需要走k-1步,所以设置两个参数,slow和fast,先让fast走k-1步,再让slow和fast同时走,这样fast就比slow多走了k-1步。这样一来,当fast走到最后一个的时候,slow正好走到倒数第k个节点。
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
{
// write code here
if(pListHead==NULL)
return NULL;
struct ListNode* slow,*fast;
slow=fast=pListHead;
while (--k) //先走k-1步
{
if (fast->next==NULL) //如果链表没有k这么大,就提前跳出循环
return NULL;
fast=fast->next;
}
while (fast->next) //先走k-1步,就要当fast->next==null的时候跳出循环
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。
https://leetcode.cn/problems/merge-two-sorted-lists/description/
解题思路一不带哨兵位:
创建一个tail和head节点,比较cur1和cur2的大小,谁小的就尾插到tail后面,当其中一个链表走完的时候跳出循环,再用判断是否两个链表都走完了,将剩余的链表直接接在tail后面。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//如果其中一个链表为空,直接返回另一个链表
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
struct ListNode* cur1 = list1,*cur2 = list2;
struct ListNode* head,*tail;
head=tail=NULL;
while(cur1 && cur2)
{
//依次比较,取小的尾插
if(cur1->val < cur2->val)
{
if(head==NULL)
{
head=tail=cur1;
}
else
{
tail->next=cur1;
tail=tail->next;
}
cur1=cur1->next;
}
else
{
if(head==NULL)
{
head=tail=cur2;
}
else
{
tail->next=cur2;
tail=tail->next;
}
cur2=cur2->next;
}
}
//如果其中一个list排完序,另一个没排完,则把另一个继续排完
if(cur1)
tail->next=cur1;
if(cur2)
tail->next=cur2;
return head;
}
解题思路二:带哨兵位
使用哨兵位将哨兵位的next指向头节点,tail初始化与哨兵位一样next指向null,这样就省去了判断刚开始需要判断head和tail是否为null的步骤,只需要将两个链表中val小的节点依次尾插到tail后面即可,第一步tail的next就是指向头节点,这样哨兵位的next就是头节点,最后将guard的next,即为新链表的头节点赋值给head,再返回即可。
这里需要注意的是,哨兵位最后进行malloc,因为这样的话这样出了作用域不会销毁。
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
//如果其中一个链表为空,直接返回另一个链表
if(list1==NULL)
return list2;
if(list2==NULL)
return list1;
struct ListNode* cur1 = list1,*cur2 = list2;
struct ListNode* head,*tail,*guard;
guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//哨兵位最好malloc,这样出了作用域不会销毁
tail->next=NULL;
while(cur1 && cur2)
{
//依次比较,取小的尾插
if(cur1->val < cur2->val)
{
tail->next=cur1;
tail=tail->next;
cur1=cur1->next;
}
else
{
tail->next=cur2;
tail=tail->next;
cur2=cur2->next;
}
}
//如果其中一个list排完序,另一个没排完,则把另一个继续排完
if(cur1)
tail->next=cur1;
if(cur2)
tail->next=cur2;
head=guard->next;//guard->next指向头节点,将其赋值给head
free(guard);
return head;
}
6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。
https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70?tpId=8&&tqId=11004&rp=2&ru=/activity/oj&qru=/ta/cracking-the-coding-interview/question-ranking
解题思路:
将val小于x的节点放在一个链表,将val大于等于x的放在另一个链表,最后再将val大于等于x的链表放在小于x的后面。
注意:最后需要将大于等于x链表的最后一个节点gtail的next置为NULL,否则会导致链表形成回环。如下图所示
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstddef>
#include <cstdlib>
#include <malloc.h>
class Partition {
public:
ListNode* partition(ListNode* pHead, int x) {
// write code here
struct ListNode* lGuard,*ltail;//小于x的尾插到一个链表
struct ListNode* gGuard,*gtail;//大于等于x的尾插到一个链表
struct ListNode* cur=pHead;
lGuard=ltail=(struct ListNode*)malloc(sizeof(struct ListNode));//哨兵位malloc
gGuard=gtail=(struct ListNode*)malloc(sizeof(struct ListNode));
ltail->next=gtail->next=NULL;//初始化
while (cur) //遍历原来的链表
{
if(cur->val<x)//小于x的尾插到一个链表
{
ltail->next=cur;
ltail=ltail->next;
}
else //大于等于x的尾插到一个链表
{
gtail->next=cur;
gtail=gtail->next;
}
cur=cur->next;
}
ltail->next=gGuard->next;//将大于等于x的链表尾插到小于x的链表后面
gtail->next=NULL;//注意:将链表的最后一位的next置为null,否则gtail的next很可能还指向之前的节点,这样会形成回环。
pHead = lGuard->next;//重新换头节点
//释放哨兵位防止内存泄漏
free(lGuard);
free(gGuard);
return pHead;
}
};
7. 链表的回文结构。
https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId=49&&tqId=29370&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking
解题思路:
1.找到中间节点
2.从中间节点开始,对后半段逆置
3.前半段和后半段进行比较
注意:比较时其中一个链表指向空时跳出循环,注意前半段的最后一个节点也是中间节点,不会因为后半段的逆置改变前半段的最后一个节点的next。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstddef>
//查找中间节点
struct ListNode *middleNode(ListNode* head)
{
struct ListNode *slow,*fast;
slow=fast=head;
while (fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
//逆置
struct ListNode* reverseList(ListNode* head)
{
if (head==NULL)
return NULL;
struct ListNode* cur,*newhead;
cur=head;
newhead=NULL;
while (cur)
{
struct ListNode* next=cur->next;
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
}
class PalindromeList {
public:
bool chkPalindrome(ListNode* head)
{
struct ListNode* mid = middleNode(head);
struct ListNode* rhead = reverseList(mid);
while (head&&rhead) //其中一个为空跳出循环,注意前半段的最后一个节点也是中间节点,不会因为后半段的逆置改变前半段的最后一个节点的next
{
if (head->val!=rhead->val)
return false;
head=head->next;
rhead=rhead->next;
}
return true;
}
};
8. 输入两个链表,找出它们的第一个公共结点。
https://leetcode.cn/problems/intersection-of-two-linked-lists/
解题思路:
1.先用两个tail和len分别计算俩个链表的长度
2.判断两个链表的最后节点是不是相同,如果两个链表有交点,那么他们的最后一个节点一定是相同的,因为每个节点只有一个next,最差的情况也是最后一个节点是交点,不相同的话直接放回NULL。
3.让长的链表先走差距步
4.再让两个链表同时走,第一个相同的地址就是交点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode *tailA,*tailB;
tailA=headA;
tailB=headB;
//计算两个链表的长度
int lenA=1;//这里不要让len初始化为0,因为这样计算链表长度循环条件是tail!=NULL,就无法判断接下来两个链表最后一个节点是否相同这一步
int lenB=1;
while(tailA->next)
{
tailA=tailA->next;
++lenA;
}
while(tailB->next)
{
tailB=tailB->next;
++lenB;
}
//如果两个链表有交点,那么他们的最后一个节点一定是相同的,因为每个节点只有一个next,最差的情况也是最后一个节点是交点
if(tailA!=tailB)
return NULL;
//让长的链表先走差距步
int gap=abs(lenA-lenB);
struct ListNode *shortlist=headA;
struct ListNode *longlist=headB;
if(lenA>lenB)
{
longlist=headA;
shortlist=headB;
}
while(gap--)
{
longlist=longlist->next;
}
//再让两个链表同时走
while(longlist!=shortlist)
{
longlist=longlist->next;
shortlist=shortlist->next;
}
return longlist;
}
9. 给定一个链表,判断链表中是否有环。
https://leetcode.cn/problems/linked-list-cycle/description/
解题思路:
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,
如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
1.为什么slow走一步,fast走两步,他们会相遇?
其实这就是一个相对速度的问题,假设slow进环的时候fast和slow之间的距离是N,fast和slow的距离从N开始缩小,每次缩小1,当距离为0的时候就一定可以追上
2.slow走一步,fast走x步(x>=3),他们会不会相遇?
假设fast每次走3步,这时候就需要分两种情况,如果N是偶数,每次距离缩小2,N-2,N-4…2,0,刚好可以追上。但如果N为奇数,每次距离减少2,最后slow和fast的距离会变成-1,也就是变成环的周长C-1,如果C-1是偶数的话,在下一轮追击就可以追上,如果C-1是奇数的话,就会一直循环追击,永远也追不上。
bool hasCycle(struct ListNode *head) {
struct ListNode *slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
return true;
}
return false;
}
10. 给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL
https://leetcode.cn/problems/linked-list-cycle-ii/description/
解题思路一:
设起始点到入口点的距离为L,slow走的距离是L+X,其中X在[0,C)之间,因为slow走一圈,fast就走了两圈,所以不可能slow走超过一圈,所以X在[0,C)之间。
而fast在slow进入环之前可能以及走了n圈,所以fast走的距离是L+nC+X;
再由fast和slow走的距离关系列出式子:2(L+X)=L+n*C+X,化简得L=(n-1)*C+C-X。
这个表达式可以得出一个结论,一个指针从起始点走,一个指针从相遇点走,会在入口点处相遇。
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow,*fast;
slow=fast=head;
while(fast&&fast->next)//跳出循环说明不带环,返回NULL
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)//找到相遇点
{
struct ListNode *meet=slow;
struct ListNode *start=head;
while(meet!=start)//找入口点
{
meet=meet->next;
start=start->next;
}
return meet;
}
}
return NULL;
}
解题思路二:
转化为两个链表求交点
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) //求交点
{
struct ListNode * tailA,*tailB;
tailA=headA;
tailB=headB;
int lenA=1;
int lenB=1;
while(tailA->next)
{
tailA=tailA->next;
lenA++;
}
while(tailB->next)
{
tailB=tailB->next;
lenB++;
}
if(tailA!=tailB)
return NULL;
int gap=abs(lenA-lenB);
struct ListNode *longlist=headA,*shortlist=headB;
if(lenA<lenB)
{
longlist=headB;
shortlist=headA;
}
while(gap--)
{
longlist=longlist->next;
}
while(longlist!=shortlist)
{
longlist=longlist->next;
shortlist=shortlist->next;
}
return longlist;
}
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
{
//转换成It1和It2求交点
struct ListNode *meet=slow;
struct ListNode *It1=head;
struct ListNode *It2=meet->next;
meet->next=NULL;//把相遇点的next置NULL
return getIntersectionNode(It1,It2);
}
}
return NULL;
}
11. 给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点
或空结点。要求返回这个链表的深度拷贝。
https://leetcode.cn/problems/copy-list-with-random-pointer/description/
解题思路:
- 插入拷贝节点在原节点后面
2. 拷贝节点的random等于原节点random的next
这一步是重点,因为在上一步进行插入拷贝节点在源节点之后,每个源节点的next就是自己的拷贝节点,而每个源节点的random的next就是源节点的random的拷贝节点。所以拷贝节点的random就是自己源节点random的next。
- 拷贝节点下来,恢复原链表,链接成新链表
将copy的节点进行尾插
/**
* Definition for a Node.
* struct Node {
* int val;
* struct Node *next;
* struct Node *random;
* };
*/
struct Node* copyRandomList(struct Node* head)
{
struct Node* cur = head;
//1.插入拷贝节点在原节点后面
while(cur)
{
struct Node* copy = (struct Node *)malloc(sizeof(struct Node));//malloc出了作用域不会销毁
copy->val=cur->val;
struct Node* next = cur->next;
cur->next=copy;
copy->next=next;
cur=next;
}
//2.拷贝节点的random等于原节点random的next
cur = head;
while(cur)
{
struct Node* copy = cur->next;
if(cur->random==NULL)
{
copy->random=NULL;
}
else
{
copy->random=cur->random->next;
}
cur=copy->next;
}
//3.拷贝节点下来,恢复原链表,链接成新链表
cur = head;
struct Node* copyhead,*copytail;
copyhead = copytail = NULL;
while(cur)
{
struct Node* copy = cur->next;
struct Node* next = copy->next;
if(copyhead==NULL)
{
copyhead=copytail=copy;
}
else
{
copytail->next=copy;
copytail=copytail->next;
}
//恢复原链表
cur->next=next;
cur=next;
}
return copyhead;
}