目录
1. 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.struct ListNode* middleNode(struct ListNode* head){ struct ListNode* val=head; struct ListNode* valq=head; while(valq!=NULL&&valq->next!=NULL) { valq=valq->next->next; val=val->next; } return val; }
解题思路: 通过快慢指针找到中间节点,快指针每次走两步,慢指针每次走一步,当快指针走到结尾的时候,慢指针正好走到中间位置
2. 反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* reverseList(struct ListNode* head){ if(head == NULL) return NULL; struct ListNode* n1,*n2,*n3; n1 = NULL; n2 = head; n3 = n2->next; while(n2) { // 倒指向 n2->next = n1; // 迭代 n1 = n2; n2 = n3; if(n3) n3 = n3->next; } return n1; }
// 取节点头插的思想完成逆置 struct ListNode* reverseList(struct ListNode* head) { struct ListNode* newhead = NULL; struct ListNode* cur = head; while(cur) { struct ListNode* next = cur->next; //头插新节点,更新头 cur->next = newhead; newhead = cur; cur = next; } return newhead; }
解题思路: 1. 三指针翻转法: 记录连续的三个节点,原地修改节点指向
此题一般常用的方法有两种,三指针翻转法和头插法 2. 头插法: 每一个节点都进行头插
3. 移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]解题方式:哨兵结点的使用有利于链表代码的标准化,可以减少一些额外的分支判断。
解题思路:从头节点开始进行元素删除,每删除一个元素,需要重新链接节点struct ListNode* removeElements(struct ListNode* head, int val) { //dummy哑结点 struct ListNode* dummy = malloc(sizeof(struct ListNode)); dummy->next = head; struct ListNode* cur = dummy; while(cur->next) { if(cur->next->val == val) { struct ListNode* next = cur->next->next; free(cur->next); cur->next = next; } else cur = cur->next; } return dummy->next; }
struct ListNode* removeElements(struct ListNode* head, int val) { if(head == NULL) return NULL; struct ListNode* cur = head; struct ListNode* prev = NULL; while(cur) { //如果当前节点是需要删除的节点 if(cur->val == val) { //首先保存下一个节点 struct ListNode* next = cur->next; //如果删除的为头节点,更新头节点 //否则让当前节点的前趋节点链接next节点 if(prev == NULL) { head = cur->next; } else { prev->next = cur->next; } //释放当前节点,让cur指向next free(cur); cur = next; } else { //如果cur不是需要删除的节点,则更新prev,cur prev = cur; cur = cur->next; } } return head; }
4. 链表分割
描述:
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
解题思路 创建两个链表,分别存放小于x的节点和大于等于x的节点,分别进行尾插
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) {} };*/ class Partition { public: ListNode* partition(ListNode* pHead, int x) { struct ListNode* lessval,*greatval,*greathead,*lesshead; greathead=greatval=(struct ListNode*)malloc(sizeof(struct ListNode)); lesshead=lessval=(struct ListNode*)malloc(sizeof(struct ListNode)); greatval->next=NULL; lessval->next=NULL; struct ListNode* cur=pHead; while(cur) { if(cur->val<x) { lessval->next=cur; lessval=lessval->next; } else { greatval->next=cur; greatval=greatval->next; } cur=cur->next; } lessval->next=greathead->next; greatval->next=NULL; struct ListNode* head=lesshead->next; free(greathead); free(lesshead); return head; } };
5. 相交链表
题目条件:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
解题思路: 此题可以先计算出两个链表的长度,让长的链表先走相差的长度,然后两个链表同时走,直到遇到相同的节点,即为第一个公共节点
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { int lenA=1,lenB=1; struct ListNode* curA=headA; struct ListNode* curB=headB; while(curA) { curA=curA->next; lenA++; } while(curB) { curB=curB->next; lenB++; } int gap=abs(lenA-lenB); if(curA!=curB) { return NULL; } struct ListNode* longlist= headA; struct ListNode* shortlist= headB; if(lenA<lenB) { longlist=headB; shortlist=headA; } //长链表先走gap步 while(gap--) { longlist=longlist->next; } while(longlist!=shortlist) { longlist=longlist->next; shortlist=shortlist->next; } return shortlist; }
6. 环形链表
题目条件:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
解题思路: 定义快慢指针fast,slow, 如果链表确实有环,fast指针一定会在环内追上slow指针
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode Node; bool hasCycle(struct ListNode *head) { Node* slow = head; Node* fast = head; while(fast && fast->next) { slow = slow->next; fast = fast->next->next; if(slow == fast) return true; } return false; }
考虑奇偶情况,如果是奇数那么fast将会指向空造成报错;
7. 环形链表 II
题目条件:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。解答:slow进环以后,fast在一圈之内,一定追上了slow,追击过程每次缩小一,他们的相对距离最多一圈
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *detectCycle(struct ListNode *head) { struct ListNode *fast=head; struct ListNode *slow=head; while(fast&&fast->next) { fast=fast->next->next; slow=slow->next; if(slow==fast) { struct ListNode *meet=slow; while(meet!=head) { meet=meet->next; head=head->next; } return meet; } } return false; }
证明:为什么快指针每次走两步,慢指针走一步可以?
结论:一定可以追上。
假设:slow与fast距离为N,这时fast真正开始追击slow,fast每次走2步,slow每次走1步,所以每行进一个过程则slow与fast间的距离减一;N->N-1->N-2->...->0,当距离为0时则追上。
证明:快指针一次走3步,走4步,...n步行吗?
假设:fast真正开始追击slow,fast一次走三步,slow一次走1步,他们之间距离每次缩小2。
8.复制带随机指针的链表
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。你的代码 只 接受原链表的头节点 head 作为传入参数。解答:
1.拷贝链表的每一个节点,拷贝的节点先链接到被拷贝节点的后面
2.复制随机指针的链接:拷贝节点的随机指针指向被拷贝节点随机指针的下一个位置
3.拆解链表,把拷贝的链表从原链表中拆解出来 /** * Definition for a Node. * struct Node { * int val; * struct Node *next; * struct Node *random; * }; */ struct Node* copyRandomList(struct Node* head) { //1.拷贝链表,并插入到原节点的后面 struct Node* cur=head; while(cur) { struct Node* copy=(struct Node*)malloc(sizeof(struct Node)); copy->val=cur->val; copy->next=cur->next; cur->next=copy; cur=copy->next; } // 2.置拷贝节点的random cur = head; while(cur) { struct Node* copy=cur->next; if(cur->random != NULL) copy->random = cur->random->next; else copy->random = NULL; cur = copy->next; } // 3.解拷贝节点,链接拷贝节点 cur=head; struct Node* copyhead=NULL; struct Node* copytail=NULL; //尾插 while(cur) { struct Node* copy=cur->next; struct Node* next=copy->next; if(copytail==NULL) { copyhead=copytail=copy; } else { copytail->next=copy; copytail=copytail->next; } cur->next=next; cur=next; } return copyhead; }